mirror of
https://github.com/natekspencer/hacs-oasis_mini.git
synced 2025-12-06 18:44:14 -05:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06a1e43295 | ||
|
|
f50320378b | ||
|
|
505fca3635 | ||
|
|
cdca084212 | ||
|
|
dfaeb382da | ||
|
|
8a2dc8e9bc | ||
|
|
8467c50215 | ||
|
|
7d7675dcb1 | ||
|
|
fb360be616 | ||
|
|
4336f658c4 | ||
|
|
50773c582c | ||
|
|
461165673c | ||
|
|
8d3cc00ebc | ||
|
|
c4fd6a7ef6 |
17
.github/workflows/validate.yaml
vendored
17
.github/workflows/validate.yaml
vendored
@@ -1,22 +1,25 @@
|
||||
name: Validate repo
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
hassfest:
|
||||
name: Validate with hassfest
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: "actions/checkout@v4"
|
||||
- uses: "home-assistant/actions/hassfest@master"
|
||||
- uses: actions/checkout@v6
|
||||
- uses: home-assistant/actions/hassfest@master
|
||||
|
||||
hacs:
|
||||
name: Validate with HACS
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: "hacs/action@main"
|
||||
- uses: hacs/action@main
|
||||
with:
|
||||
category: "integration"
|
||||
category: integration
|
||||
|
||||
@@ -17,7 +17,7 @@ from homeassistant.components.media_player import (
|
||||
|
||||
from .pyoasiscontrol import OasisCloudClient
|
||||
from .pyoasiscontrol.const import TRACKS
|
||||
from .pyoasiscontrol.utils import get_track_ids_from_playlist, get_url_for_image
|
||||
from .pyoasiscontrol.utils import get_image_url_from_track, get_track_ids_from_playlist
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -128,7 +128,7 @@ def build_tracks_root() -> BrowseMedia:
|
||||
media_content_type=MEDIA_TYPE_OASIS_TRACK,
|
||||
can_play=True,
|
||||
can_expand=False,
|
||||
thumbnail=get_url_for_image(meta.get("image")),
|
||||
thumbnail=get_image_url_from_track(meta),
|
||||
)
|
||||
for track_id, meta in TRACKS.items()
|
||||
]
|
||||
@@ -156,15 +156,15 @@ def build_track_item(track_id: int) -> BrowseMedia:
|
||||
media_content_type=MEDIA_TYPE_OASIS_TRACK,
|
||||
can_play=True,
|
||||
can_expand=False,
|
||||
thumbnail=get_url_for_image(meta.get("image")),
|
||||
thumbnail=get_image_url_from_track(meta),
|
||||
)
|
||||
|
||||
|
||||
def get_first_image_for_playlist(playlist: dict[str, Any]) -> str | None:
|
||||
"""Get the first image from a playlist dictionary."""
|
||||
for track in playlist.get("patterns") or []:
|
||||
if image := track.get("image"):
|
||||
return get_url_for_image(image)
|
||||
if image := get_image_url_from_track(track):
|
||||
return image
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -340,9 +340,7 @@ class OasisDeviceMediaPlayerEntity(OasisDeviceEntity, MediaPlayerEntity):
|
||||
return
|
||||
|
||||
if enqueue == MediaPlayerEnqueue.REPLACE:
|
||||
await device.async_stop()
|
||||
await device.async_set_playlist(track_ids)
|
||||
await device.async_play()
|
||||
await device.async_set_playlist(track_ids, start_playing=True)
|
||||
return
|
||||
|
||||
insert_at = (device.playlist_index or 0) + 1
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, Callable, Final, Iterable
|
||||
|
||||
@@ -11,6 +12,7 @@ from .const import (
|
||||
LED_EFFECTS,
|
||||
STATUS_CODE_MAP,
|
||||
STATUS_ERROR,
|
||||
STATUS_PLAYING,
|
||||
STATUS_SLEEPING,
|
||||
TRACKS,
|
||||
)
|
||||
@@ -19,7 +21,8 @@ from .utils import (
|
||||
_parse_int,
|
||||
create_svg,
|
||||
decrypt_svg_content,
|
||||
get_url_for_image,
|
||||
get_image_url_from_track,
|
||||
now,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # avoid runtime circular imports
|
||||
@@ -139,6 +142,9 @@ class OasisDevice:
|
||||
self._track: dict | None = None
|
||||
self._track_task: asyncio.Task | None = None
|
||||
|
||||
# Diagnostic metadata
|
||||
self.last_updated: datetime | None = None
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
"""
|
||||
@@ -258,6 +264,8 @@ class OasisDevice:
|
||||
if changed:
|
||||
self._notify_listeners()
|
||||
|
||||
self.last_updated = now()
|
||||
|
||||
def parse_status_string(self, raw_status: str) -> dict[str, Any] | None:
|
||||
"""
|
||||
Parse a semicolon-separated device status string into a structured state dictionary.
|
||||
@@ -407,9 +415,7 @@ class OasisDevice:
|
||||
Returns:
|
||||
str: Full URL to the track image or `None` if no image is available.
|
||||
"""
|
||||
if track := self.track:
|
||||
return get_url_for_image(track.get("image"))
|
||||
return None
|
||||
return get_image_url_from_track(self.track)
|
||||
|
||||
@property
|
||||
def track_name(self) -> str | None:
|
||||
@@ -647,21 +653,33 @@ class OasisDevice:
|
||||
client = self._require_client()
|
||||
await client.async_send_add_joblist_command(self, tracks)
|
||||
|
||||
async def async_set_playlist(self, playlist: int | Iterable[int]) -> None:
|
||||
async def async_set_playlist(
|
||||
self, playlist: int | Iterable[int], *, start_playing: bool | None = None
|
||||
) -> None:
|
||||
"""
|
||||
Set the device's playlist to the provided track or tracks.
|
||||
|
||||
Accepts a single track ID or an iterable of track IDs and replaces the device's playlist by sending the corresponding command to the attached client.
|
||||
Accepts a single track ID or an iterable of track IDs, stops the device,
|
||||
replaces the playlist, and resumes playback based on the `start_playing`
|
||||
parameter or, if unspecified, the device's prior playing state.
|
||||
|
||||
Parameters:
|
||||
playlist (int | Iterable[int]): A single track ID or an iterable of track IDs to set as the new playlist.
|
||||
playlist (int | Iterable[int]):
|
||||
A single track ID or an iterable of track IDs to set as the new playlist.
|
||||
start_playing (bool | None, keyword-only):
|
||||
Whether to start playback after updating the playlist. If None (default),
|
||||
playback will resume only if the device was previously playing and the
|
||||
new playlist is non-empty.
|
||||
"""
|
||||
if isinstance(playlist, int):
|
||||
playlist_list = [playlist]
|
||||
else:
|
||||
playlist_list = list(playlist)
|
||||
playlist = [playlist] if isinstance(playlist, int) else list(playlist)
|
||||
if start_playing is None:
|
||||
start_playing = self.status_code == STATUS_PLAYING
|
||||
|
||||
client = self._require_client()
|
||||
await client.async_send_set_playlist_command(self, playlist_list)
|
||||
await client.async_send_stop_command(self) # needed before replacing playlist
|
||||
await client.async_send_set_playlist_command(self, playlist)
|
||||
if start_playing and playlist:
|
||||
await client.async_send_play_command(self)
|
||||
|
||||
async def async_set_repeat_playlist(self, repeat: bool) -> None:
|
||||
"""
|
||||
|
||||
@@ -13622,7 +13622,7 @@
|
||||
"pattern_id": null,
|
||||
"clean_type": "clean_id",
|
||||
"png_image": "2025/06/a008fed534a5d78dceda0072850cfc82.png",
|
||||
"author": "Thalia soares",
|
||||
"author": "Thalia vitoria",
|
||||
"reduced_svg_content_new": 3823
|
||||
},
|
||||
"5965": {
|
||||
|
||||
@@ -22,6 +22,8 @@ COLOR_LIGHT_SHADE = ("#FFFFFF", "#86888F")
|
||||
COLOR_MEDIUM_SHADE = ("#E5E2DE", "#86888F")
|
||||
COLOR_MEDIUM_TINT = ("#B8B8B8", "#FFFFFF")
|
||||
|
||||
IMAGE_URL = "https://app.grounded.so/uploads/{image}"
|
||||
|
||||
|
||||
def _bit_to_bool(val: str) -> bool:
|
||||
"""Convert a bit string to bool."""
|
||||
@@ -200,15 +202,17 @@ def decrypt_svg_content(svg_content: dict[str, str]):
|
||||
return decrypted
|
||||
|
||||
|
||||
def get_image_url_from_track(track: dict[str, Any] | None) -> str | None:
|
||||
"""Get the image URL from a track."""
|
||||
if not isinstance(track, dict):
|
||||
return None
|
||||
return IMAGE_URL.format(image=image) if (image := track.get("image")) else None
|
||||
|
||||
|
||||
def get_track_ids_from_playlist(playlist: dict[str, Any]) -> list[int]:
|
||||
"""Get a list of track ids from a playlist."""
|
||||
return [track["id"] for track in (playlist.get("patterns") or []) if "id" in track]
|
||||
|
||||
|
||||
def get_url_for_image(image: str | None) -> str | None:
|
||||
"""Get the full URL for an image."""
|
||||
return f"https://app.grounded.so/uploads/{image}" if image else None
|
||||
|
||||
|
||||
def now() -> datetime:
|
||||
return datetime.now(UTC)
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
@@ -68,6 +71,13 @@ DESCRIPTORS = [
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="last_updated",
|
||||
translation_key="last_updated",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
]
|
||||
DESCRIPTORS.extend(
|
||||
SensorEntityDescription(
|
||||
@@ -84,11 +94,6 @@ class OasisDeviceSensorEntity(OasisDeviceEntity, SensorEntity):
|
||||
"""Oasis device sensor entity."""
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | None:
|
||||
"""
|
||||
Provide the current sensor value from the underlying device.
|
||||
|
||||
Returns:
|
||||
`str` with the sensor's current value, or `None` if the attribute is not present or has no value. The value is taken from the device attribute named by the entity description's `key`.
|
||||
"""
|
||||
def native_value(self) -> str | int | float | datetime | None:
|
||||
"""Provide the current sensor value from the underlying device."""
|
||||
return getattr(self.device, self.entity_description.key)
|
||||
|
||||
@@ -125,6 +125,9 @@
|
||||
"18": "Error while downloading the job file"
|
||||
}
|
||||
},
|
||||
"last_updated": {
|
||||
"name": "Last updated"
|
||||
},
|
||||
"led_color_id": {
|
||||
"name": "LED color ID"
|
||||
},
|
||||
|
||||
@@ -125,6 +125,9 @@
|
||||
"18": "Error while downloading the job file"
|
||||
}
|
||||
},
|
||||
"last_updated": {
|
||||
"name": "Last updated"
|
||||
},
|
||||
"led_color_id": {
|
||||
"name": "LED color ID"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user