mirror of
https://github.com/natekspencer/hacs-oasis_mini.git
synced 2025-11-12 23:23:51 -05:00
Add support for enqueue options in media_player.play_media service and other minor improvements
This commit is contained in:
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
|
from homeassistant.components.image import Image, ImageEntity, ImageEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@@ -42,6 +42,7 @@ class OasisMiniImageEntity(OasisMiniEntity, ImageEntity):
|
|||||||
)
|
)
|
||||||
return self._cached_image.content
|
return self._cached_image.content
|
||||||
|
|
||||||
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
if self._track_id != self.device.track_id or (
|
if self._track_id != self.device.track_id or (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.media_player import (
|
from homeassistant.components.media_player import (
|
||||||
|
MediaPlayerEnqueue,
|
||||||
MediaPlayerEntity,
|
MediaPlayerEntity,
|
||||||
MediaPlayerEntityDescription,
|
MediaPlayerEntityDescription,
|
||||||
MediaPlayerEntityFeature,
|
MediaPlayerEntityFeature,
|
||||||
@@ -39,6 +40,7 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
|
|||||||
| MediaPlayerEntityFeature.PREVIOUS_TRACK
|
| MediaPlayerEntityFeature.PREVIOUS_TRACK
|
||||||
| MediaPlayerEntityFeature.NEXT_TRACK
|
| MediaPlayerEntityFeature.NEXT_TRACK
|
||||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||||
|
| MediaPlayerEntityFeature.MEDIA_ENQUEUE
|
||||||
| MediaPlayerEntityFeature.CLEAR_PLAYLIST
|
| MediaPlayerEntityFeature.CLEAR_PLAYLIST
|
||||||
| MediaPlayerEntityFeature.REPEAT_SET
|
| MediaPlayerEntityFeature.REPEAT_SET
|
||||||
)
|
)
|
||||||
@@ -144,7 +146,11 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
|
|||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
async def async_play_media(
|
async def async_play_media(
|
||||||
self, media_type: MediaType | str, media_id: str, **kwargs: Any
|
self,
|
||||||
|
media_type: MediaType | str,
|
||||||
|
media_id: str,
|
||||||
|
enqueue: MediaPlayerEnqueue | None = None,
|
||||||
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Play a piece of media."""
|
"""Play a piece of media."""
|
||||||
if media_id not in TRACKS:
|
if media_id not in TRACKS:
|
||||||
@@ -157,15 +163,33 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
|
|||||||
media_id,
|
media_id,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
media_id = int(media_id)
|
track = int(media_id)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
raise ServiceValidationError(f"Invalid media: {media_id}") from err
|
raise ServiceValidationError(f"Invalid media: {media_id}") from err
|
||||||
|
|
||||||
await add_and_play_track(self.device, media_id)
|
device = self.device
|
||||||
|
enqueue = MediaPlayerEnqueue.NEXT if not enqueue else enqueue
|
||||||
|
if enqueue == MediaPlayerEnqueue.REPLACE:
|
||||||
|
await device.async_set_playlist([track])
|
||||||
|
else:
|
||||||
|
await device.async_add_track_to_playlist(track)
|
||||||
|
|
||||||
|
if enqueue in (MediaPlayerEnqueue.NEXT, MediaPlayerEnqueue.PLAY):
|
||||||
|
# Move track to next item in the playlist
|
||||||
|
if (idx := (len(device.playlist) - 1)) != device.playlist_index:
|
||||||
|
if idx != (nxt := min(device.playlist_index + 1, len(device.playlist))):
|
||||||
|
await device.async_move_track(idx, nxt)
|
||||||
|
if enqueue == MediaPlayerEnqueue.PLAY:
|
||||||
|
await device.async_change_track(nxt)
|
||||||
|
|
||||||
|
if device.status_code != 4:
|
||||||
|
await device.async_play()
|
||||||
|
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
async def async_clear_playlist(self) -> None:
|
async def async_clear_playlist(self) -> None:
|
||||||
"""Clear players playlist."""
|
"""Clear players playlist."""
|
||||||
await self.device.async_set_playlist([0])
|
await self.device.async_clear_playlist()
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import logging
|
|||||||
from typing import Any, Awaitable, Callable, Final
|
from typing import Any, Awaitable, Callable, Final
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientResponseError, ClientSession
|
||||||
|
import async_timeout
|
||||||
|
|
||||||
from .utils import _bit_to_bool
|
from .utils import _bit_to_bool
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ STATUS_CODE_MAP = {
|
|||||||
0: "booting", # maybe?
|
0: "booting", # maybe?
|
||||||
2: "stopped",
|
2: "stopped",
|
||||||
3: "centering",
|
3: "centering",
|
||||||
4: "running",
|
4: "playing",
|
||||||
5: "paused",
|
5: "paused",
|
||||||
9: "error",
|
9: "error",
|
||||||
11: "updating",
|
11: "updating",
|
||||||
@@ -90,6 +91,7 @@ class OasisMini:
|
|||||||
|
|
||||||
autoplay: str
|
autoplay: str
|
||||||
brightness: int
|
brightness: int
|
||||||
|
busy: bool
|
||||||
color: str
|
color: str
|
||||||
download_progress: int
|
download_progress: int
|
||||||
error: int
|
error: int
|
||||||
@@ -175,7 +177,7 @@ class OasisMini:
|
|||||||
|
|
||||||
async def async_add_track_to_playlist(self, track: int) -> None:
|
async def async_add_track_to_playlist(self, track: int) -> None:
|
||||||
"""Add track to playlist."""
|
"""Add track to playlist."""
|
||||||
if 0 in self.playlist:
|
if track and 0 in self.playlist:
|
||||||
playlist = [t for t in self.playlist if t] + [track]
|
playlist = [t for t in self.playlist if t] + [track]
|
||||||
await self.async_set_playlist(playlist)
|
await self.async_set_playlist(playlist)
|
||||||
else:
|
else:
|
||||||
@@ -188,6 +190,10 @@ class OasisMini:
|
|||||||
raise ValueError("Invalid index specified")
|
raise ValueError("Invalid index specified")
|
||||||
await self._async_command(params={"CMDCHANGETRACK": index})
|
await self._async_command(params={"CMDCHANGETRACK": index})
|
||||||
|
|
||||||
|
async def async_clear_playlist(self) -> None:
|
||||||
|
"""Clear the playlist."""
|
||||||
|
await self.async_set_playlist([0])
|
||||||
|
|
||||||
async def async_get_ip_address(self) -> str | None:
|
async def async_get_ip_address(self) -> str | None:
|
||||||
"""Get the ip address."""
|
"""Get the ip address."""
|
||||||
self._ip_address = await self._async_get(params={"GETIP": ""})
|
self._ip_address = await self._async_get(params={"GETIP": ""})
|
||||||
@@ -235,7 +241,8 @@ class OasisMini:
|
|||||||
"""Send play command."""
|
"""Send play command."""
|
||||||
if self.status_code == 15:
|
if self.status_code == 15:
|
||||||
await self.async_stop()
|
await self.async_stop()
|
||||||
await self._async_command(params={"CMDPLAY": ""})
|
if self.track_id:
|
||||||
|
await self._async_command(params={"CMDPLAY": ""})
|
||||||
|
|
||||||
async def async_reboot(self) -> None:
|
async def async_reboot(self) -> None:
|
||||||
"""Send reboot command."""
|
"""Send reboot command."""
|
||||||
@@ -294,9 +301,13 @@ class OasisMini:
|
|||||||
await self._async_command(params={"WRIWAITAFTER": option})
|
await self._async_command(params={"WRIWAITAFTER": option})
|
||||||
|
|
||||||
async def async_set_playlist(self, playlist: list[int]) -> None:
|
async def async_set_playlist(self, playlist: list[int]) -> None:
|
||||||
"""Set playlist."""
|
"""Set the playlist."""
|
||||||
|
if is_playing := (self.status_code == 4):
|
||||||
|
await self.async_stop()
|
||||||
await self._async_command(params={"WRIJOBLIST": ",".join(map(str, playlist))})
|
await self._async_command(params={"WRIJOBLIST": ",".join(map(str, playlist))})
|
||||||
self.playlist = playlist
|
self.playlist = playlist
|
||||||
|
if is_playing:
|
||||||
|
await self.async_play()
|
||||||
|
|
||||||
async def async_set_repeat_playlist(self, repeat: bool) -> None:
|
async def async_set_repeat_playlist(self, repeat: bool) -> None:
|
||||||
"""Set repeat playlist."""
|
"""Set repeat playlist."""
|
||||||
@@ -327,6 +338,9 @@ class OasisMini:
|
|||||||
"""Get cloud track info."""
|
"""Get cloud track info."""
|
||||||
try:
|
try:
|
||||||
return await self._async_cloud_request("GET", f"api/track/{track_id}")
|
return await self._async_cloud_request("GET", f"api/track/{track_id}")
|
||||||
|
except ClientResponseError as err:
|
||||||
|
if err.status == 404:
|
||||||
|
return {"id": track_id, "name": f"Unknown Title (#{track_id})"}
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
_LOGGER.exception(ex)
|
_LOGGER.exception(ex)
|
||||||
return None
|
return None
|
||||||
@@ -386,8 +400,11 @@ class OasisMini:
|
|||||||
|
|
||||||
async def _async_command(self, **kwargs: Any) -> str | None:
|
async def _async_command(self, **kwargs: Any) -> str | None:
|
||||||
"""Send a command to the device."""
|
"""Send a command to the device."""
|
||||||
result = await self._async_get(**kwargs)
|
with async_timeout.timeout(5):
|
||||||
_LOGGER.debug("Result: %s", result)
|
while self.busy:
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
result = await self._async_get(**kwargs)
|
||||||
|
_LOGGER.debug("Result: %s", result)
|
||||||
|
|
||||||
async def _async_get(self, **kwargs: Any) -> str | None:
|
async def _async_get(self, **kwargs: Any) -> str | None:
|
||||||
"""Perform a GET request."""
|
"""Perform a GET request."""
|
||||||
|
|||||||
@@ -918,5 +918,30 @@
|
|||||||
"name": "Yin yang",
|
"name": "Yin yang",
|
||||||
"author": "001547.d33e09ec63fb4259a31a494ad194e028.0314",
|
"author": "001547.d33e09ec63fb4259a31a494ad194e028.0314",
|
||||||
"image": "2024/07/36fe669628c5e4dfd6d33a263196a750.svg"
|
"image": "2024/07/36fe669628c5e4dfd6d33a263196a750.svg"
|
||||||
|
},
|
||||||
|
"611": {
|
||||||
|
"name": "Flow Snake",
|
||||||
|
"author": "Matt Flood",
|
||||||
|
"image": "2024/07/c5bf122bc1c8f7ef44caff6a0856bd22.svg"
|
||||||
|
},
|
||||||
|
"576": {
|
||||||
|
"name": "Flower Wipe",
|
||||||
|
"author": "Shannon Miller",
|
||||||
|
"image": "2024/07/27205730092d3b5c866bd53b9d26be97.svg"
|
||||||
|
},
|
||||||
|
"711": {
|
||||||
|
"name": "Looping Fractal Spirograph",
|
||||||
|
"author": "Daniel Moton",
|
||||||
|
"image": "2024/07/deb3f3c00b2cb65243099f41e00b0af5.svg"
|
||||||
|
},
|
||||||
|
"613": {
|
||||||
|
"name": "Reverse",
|
||||||
|
"author": "Kari Hobbs",
|
||||||
|
"image": "2024/07/6c7ca5f1b8f94cf650b1793b1c2c81bb.svg"
|
||||||
|
},
|
||||||
|
"555": {
|
||||||
|
"name": "Wavy dude",
|
||||||
|
"author": "Codie Johnston",
|
||||||
|
"image": "2024/07/8d252eed81664c520381cc21c8c3d86d.svg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ from typing import Any, Awaitable, Callable
|
|||||||
|
|
||||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ class OasisMiniSelectEntity(OasisMiniEntity, SelectEntity):
|
|||||||
await self.entity_description.select_fn(self.device, self.options.index(option))
|
await self.entity_description.select_fn(self.device, self.options.index(option))
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
new_value = self.entity_description.current_value(self.device)
|
new_value = self.entity_description.current_value(self.device)
|
||||||
@@ -89,7 +90,7 @@ DESCRIPTORS = (
|
|||||||
OasisMiniSelectEntityDescription(
|
OasisMiniSelectEntityDescription(
|
||||||
key="playlist",
|
key="playlist",
|
||||||
name="Playlist",
|
name="Playlist",
|
||||||
current_value=lambda device: (device.playlist, device.playlist_index),
|
current_value=lambda device: (device.playlist.copy(), device.playlist_index),
|
||||||
select_fn=lambda device, option: device.async_change_track(option),
|
select_fn=lambda device, option: device.async_change_track(option),
|
||||||
update_handler=playlist_update_handler,
|
update_handler=playlist_update_handler,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ DESCRIPTORS = {
|
|||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key=key,
|
key=key,
|
||||||
name=key.replace("_", " ").capitalize(),
|
name=key.replace("_", " ").capitalize(),
|
||||||
|
translation_key=key,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,5 +36,23 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"status": {
|
||||||
|
"name": "Status",
|
||||||
|
"state": {
|
||||||
|
"booting": "Booting",
|
||||||
|
"stopped": "Stopped",
|
||||||
|
"centering": "Centering",
|
||||||
|
"playing": "Playing",
|
||||||
|
"paused": "Paused",
|
||||||
|
"error": "Error",
|
||||||
|
"updating": "Updating",
|
||||||
|
"downloading": "Downloading",
|
||||||
|
"live drawing": "Live drawing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,5 +36,23 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"invalid_auth": "Invalid authentication"
|
"invalid_auth": "Invalid authentication"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"sensor": {
|
||||||
|
"status": {
|
||||||
|
"name": "Status",
|
||||||
|
"state": {
|
||||||
|
"booting": "Booting",
|
||||||
|
"stopped": "Stopped",
|
||||||
|
"centering": "Centering",
|
||||||
|
"playing": "Playing",
|
||||||
|
"paused": "Paused",
|
||||||
|
"error": "Error",
|
||||||
|
"updating": "Updating",
|
||||||
|
"downloading": "Downloading",
|
||||||
|
"live drawing": "Live drawing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user