1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-11-13 23:53:51 -05:00

12 Commits
0.7.1 ... 0.7.5

Author SHA1 Message Date
Nathan Spencer
f5bf50a801 Merge pull request #18 from natekspencer/dev
Better error handling
2024-08-03 17:33:21 -06:00
Nathan Spencer
33e62528ba Better error handling 2024-08-03 17:31:30 -06:00
Nathan Spencer
3014f0f11c Merge pull request #16 from natekspencer/dev
Handle invalid index bug in play random track button
2024-08-02 12:03:07 -06:00
Nathan Spencer
a44c035828 Handle invalid index bug in play random track button 2024-08-02 12:01:27 -06:00
Nathan Spencer
31276048dc Merge pull request #15 from natekspencer/natekspencer-patch-1
Create dependabot.yml
2024-08-02 07:24:40 -06:00
Nathan Spencer
742fc26a4f Create dependabot.yml 2024-08-02 07:21:26 -06:00
Nathan Spencer
3acd45da9d Merge pull request #14 from natekspencer/dev
Revert command timeout logic
2024-07-31 21:04:57 -06:00
Nathan Spencer
a736c72c8e Revert timeout changes, I'll fix later 2024-07-31 21:03:33 -06:00
Nathan Spencer
c87bb241ef Allow reboot command even if device is busy 2024-07-31 20:55:37 -06:00
Nathan Spencer
6ee81db9d4 Merge pull request #13 from natekspencer/dev
Add support for enqueue options in media_player.play_media service and other minor improvements
2024-07-31 19:28:56 -06:00
Nathan Spencer
6d6b7929d5 Fix hassfest error 2024-07-31 19:25:02 -06:00
Nathan Spencer
cc80c295f6 Add support for enqueue options in media_player.play_media service and other minor improvements 2024-07-31 19:16:15 -06:00
15 changed files with 2061 additions and 792 deletions

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -1,6 +1,9 @@
![Release](https://img.shields.io/github/v/release/natekspencer/hacs-oasis_mini?style=for-the-badge) [![Release](https://img.shields.io/github/v/release/natekspencer/hacs-oasis_mini?style=for-the-badge)](https://github.com/natekspencer/hacs-oasis_mini/releases)
[![Buy Me A Coffee/Beer](https://img.shields.io/badge/Buy_Me_A_☕/🍺-F16061?style=for-the-badge&logo=ko-fi&logoColor=white&labelColor=grey)](https://ko-fi.com/natekspencer) [![Buy Me A Coffee/Beer](https://img.shields.io/badge/Buy_Me_A_☕/🍺-F16061?style=for-the-badge&logo=ko-fi&logoColor=white&labelColor=grey)](https://ko-fi.com/natekspencer)
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg?style=for-the-badge)](https://github.com/hacs/integration) [![HACS Custom](https://img.shields.io/badge/HACS-Custom-41BDF5.svg?style=for-the-badge)](https://github.com/hacs/integration)
![Downloads](https://img.shields.io/github/downloads/natekspencer/hacs-oasis_mini/total?style=flat-square)
![Latest Downloads](https://img.shields.io/github/downloads/natekspencer/hacs-oasis_mini/latest/total?style=flat-square)
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://brands.home-assistant.io/oasis_mini/dark_logo.png"> <source media="(prefers-color-scheme: dark)" srcset="https://brands.home-assistant.io/oasis_mini/dark_logo.png">

View File

@@ -39,7 +39,7 @@ async def async_setup_entry(
async def play_random_track(device: OasisMini) -> None: async def play_random_track(device: OasisMini) -> None:
"""Play random track.""" """Play random track."""
track = int(random.choice(list(TRACKS))) track = random.choice(list(TRACKS))
await add_and_play_track(device, track) await add_and_play_track(device, track)

View File

@@ -47,15 +47,14 @@ class OasisMiniCoordinator(DataUpdateCoordinator[str]):
if not self.device.software_version: if not self.device.software_version:
await self.device.async_get_software_version() await self.device.async_get_software_version()
data = await self.device.async_get_status() data = await self.device.async_get_status()
self.attempt = 0
await self.device.async_get_current_track_details() await self.device.async_get_current_track_details()
await self.device.async_get_playlist_details() await self.device.async_get_playlist_details()
except Exception as ex: # pylint:disable=broad-except except Exception as ex: # pylint:disable=broad-except
if self.attempt > 2 or not self.data: if self.attempt > 2 or not (data or self.data):
raise UpdateFailed( raise UpdateFailed(
f"Couldn't read from the Oasis Mini after {self.attempt} attempts" f"Couldn't read from the Oasis Mini after {self.attempt} attempts"
) from ex ) from ex
else:
self.attempt = 0
if data != self.data: if data != self.data:
self.last_updated = datetime.now() self.last_updated = datetime.now()

View File

@@ -21,7 +21,7 @@ async def add_and_play_track(device: OasisMini, track: int) -> None:
# Move track to next item in the playlist and then select it # Move track to next item in the playlist and then select it
if (index := device.playlist.index(track)) != device.playlist_index: if (index := device.playlist.index(track)) != device.playlist_index:
if index != (_next := min(device.playlist_index + 1, len(device.playlist))): if index != (_next := min(device.playlist_index + 1, len(device.playlist) - 1)):
await device.async_move_track(index, _next) await device.async_move_track(index, _next)
await device.async_change_track(_next) await device.async_change_track(_next)

View File

@@ -4,8 +4,9 @@ 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 homeassistant.helpers.typing import UNDEFINED
from .const import DOMAIN from .const import DOMAIN
from .coordinator import OasisMiniCoordinator from .coordinator import OasisMiniCoordinator
@@ -42,6 +43,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 (
@@ -51,13 +53,18 @@ class OasisMiniImageEntity(OasisMiniEntity, ImageEntity):
self._track_id = self.device.track_id self._track_id = self.device.track_id
self._progress = self.device.progress self._progress = self.device.progress
self._cached_image = None self._cached_image = None
if not self.device.access_token: if self.device.track and self.device.track.get("svg_content"):
self._attr_image_url = UNDEFINED
else:
self._attr_image_url = ( self._attr_image_url = (
f"https://app.grounded.so/uploads/{track['image']}" f"https://app.grounded.so/uploads/{track['image']}"
if (track := TRACKS.get(str(self.device.track_id))) if (
track := (self.device.track or TRACKS.get(self.device.track_id))
)
and "image" in track and "image" in track
else None else None
) )
if self.hass: if self.hass:
super()._handle_coordinator_update() super()._handle_coordinator_update()

View File

@@ -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,
@@ -22,7 +23,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN from .const import DOMAIN
from .coordinator import OasisMiniCoordinator from .coordinator import OasisMiniCoordinator
from .entity import OasisMiniEntity from .entity import OasisMiniEntity
from .helpers import add_and_play_track
from .pyoasismini.const import TRACKS from .pyoasismini.const import TRACKS
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -39,6 +39,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
) )
@@ -59,7 +60,7 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
def media_image_url(self) -> str | None: def media_image_url(self) -> str | None:
"""Image url of current playing media.""" """Image url of current playing media."""
if not (track := self.device.track): if not (track := self.device.track):
track = TRACKS.get(str(self.device.track_id)) track = TRACKS.get(self.device.track_id)
if track and "image" in track: if track and "image" in track:
return f"https://app.grounded.so/uploads/{track['image']}" return f"https://app.grounded.so/uploads/{track['image']}"
return None return None
@@ -80,7 +81,7 @@ class OasisMiniMediaPlayerEntity(OasisMiniEntity, MediaPlayerEntity):
if not self.device.track_id: if not self.device.track_id:
return None return None
if not (track := self.device.track): if not (track := self.device.track):
track = TRACKS.get(str(self.device.track_id), {}) track = TRACKS.get(self.device.track_id, {})
return track.get("name", f"Unknown Title (#{self.device.track_id})") return track.get("name", f"Unknown Title (#{self.device.track_id})")
@property @property
@@ -144,10 +145,14 @@ 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 map(str, TRACKS):
media_id = next( media_id = next(
( (
id id
@@ -157,15 +162,38 @@ 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 (index := (len(device.playlist) - 1)) != device.playlist_index:
if index != (
_next := min(device.playlist_index + 1, len(device.playlist) - 1)
):
await device.async_move_track(index, _next)
if enqueue == MediaPlayerEnqueue.PLAY:
await device.async_change_track(_next)
if (
enqueue in (MediaPlayerEnqueue.PLAY, MediaPlayerEnqueue.REPLACE)
and 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()

View File

@@ -5,8 +5,9 @@ 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
from .const import TRACKS
from .utils import _bit_to_bool from .utils import _bit_to_bool
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -15,12 +16,12 @@ 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",
13: "downloading", 13: "downloading",
15: "live drawing", 15: "live",
} }
AUTOPLAY_MAP = { AUTOPLAY_MAP = {
@@ -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,12 +177,15 @@ 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 not track:
return
if 0 in self.playlist: if 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) return await self.async_set_playlist(playlist)
else:
await self._async_command(params={"ADDJOBLIST": track}) await self._async_command(params={"ADDJOBLIST": track})
self.playlist.append(track) self.playlist.append(track)
async def async_change_track(self, index: int) -> None: async def async_change_track(self, index: int) -> None:
"""Change the track.""" """Change the track."""
@@ -188,6 +193,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([])
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 +244,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 +304,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,9 +341,12 @@ 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
async def async_cloud_get_tracks( async def async_cloud_get_tracks(
self, tracks: list[int] | None = None self, tracks: list[int] | None = None
@@ -338,6 +355,8 @@ class OasisMini:
response = await self._async_cloud_request( response = await self._async_cloud_request(
"GET", "api/track", params={"ids[]": tracks or []} "GET", "api/track", params={"ids[]": tracks or []}
) )
if not response:
return None
track_details = response.get("data", []) track_details = response.get("data", [])
while next_page_url := response.get("next_page_url"): while next_page_url := response.get("next_page_url"):
response = await self._async_cloud_request("GET", next_page_url) response = await self._async_cloud_request("GET", next_page_url)
@@ -350,17 +369,22 @@ class OasisMini:
async def async_get_current_track_details(self) -> dict | None: async def async_get_current_track_details(self) -> dict | None:
"""Get current track info, refreshing if needed.""" """Get current track info, refreshing if needed."""
if (track := self._track) and track.get("id") == self.track_id: track_id = self.track_id
if (track := self._track) and track.get("id") == track_id:
return track return track
if self.track_id: if track_id:
self._track = await self.async_cloud_get_track_info(self.track_id) self._track = await self.async_cloud_get_track_info(track_id)
if not self._track:
self._track = TRACKS.get(
track_id, {"id": track_id, "name": f"Unknown Title (#{track_id})"}
)
return self._track return self._track
async def async_get_playlist_details(self) -> dict[int, dict[str, str]]: async def async_get_playlist_details(self) -> dict[int, dict[str, str]]:
"""Get playlist info.""" """Get playlist info."""
if set(self.playlist).difference(self._playlist.keys()): if set(self.playlist).difference(self._playlist.keys()):
tracks = await self.async_cloud_get_tracks(self.playlist) tracks = await self.async_cloud_get_tracks(self.playlist)
self._playlist = { all_tracks = TRACKS | {
track["id"]: { track["id"]: {
"name": track["name"], "name": track["name"],
"author": ((track.get("author") or {}).get("person") or {}).get( "author": ((track.get("author") or {}).get("person") or {}).get(
@@ -370,6 +394,10 @@ class OasisMini:
} }
for track in tracks for track in tracks
} }
for track in self.playlist:
self._playlist[track] = all_tracks.get(
track, {"name": f"Unknown Title (#{track})"}
)
return self._playlist return self._playlist
async def _async_cloud_request(self, method: str, url: str, **kwargs: Any) -> Any: async def _async_cloud_request(self, method: str, url: str, **kwargs: Any) -> Any:
@@ -395,6 +423,13 @@ class OasisMini:
async def _async_request(self, method: str, url: str, **kwargs) -> Any: async def _async_request(self, method: str, url: str, **kwargs) -> Any:
"""Perform a request.""" """Perform a request."""
_LOGGER.debug(
"%s %s",
method,
self._session._build_url(url).update_query( # pylint: disable=protected-access
kwargs.get("params")
),
)
response = await self._session.request(method, url, **kwargs) response = await self._session.request(method, url, **kwargs)
if response.status == 200: if response.status == 200:
if response.content_type == "application/json": if response.content_type == "application/json":

View File

@@ -4,8 +4,13 @@ from __future__ import annotations
import json import json
import os import os
from typing import Final from typing import Any, Final
__TRACKS_FILE = os.path.join(os.path.dirname(__file__), "tracks.json") __TRACKS_FILE = os.path.join(os.path.dirname(__file__), "tracks.json")
with open(__TRACKS_FILE, "r", encoding="utf8") as file: try:
TRACKS: Final[dict[str, dict[str, str]]] = json.load(file) with open(__TRACKS_FILE, "r", encoding="utf8") as file:
TRACKS: Final[dict[int, dict[str, Any]]] = {
int(k): v for k, v in json.load(file).items()
}
except Exception: # ignore: broad-except
TRACKS = {}

File diff suppressed because it is too large Load Diff

View File

@@ -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)
@@ -71,7 +72,7 @@ def playlist_update_handler(entity: OasisMiniSelectEntity) -> None:
options = [ options = [
device._playlist.get(track, {}).get( device._playlist.get(track, {}).get(
"name", "name",
TRACKS.get(str(track), {}).get( TRACKS.get(track, {"id": track, "name": f"Unknown Title (#{track})"}).get(
"name", "name",
device.track["name"] device.track["name"]
if device.track and device.track["id"] == track if device.track and device.track["id"] == track
@@ -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,
), ),

View File

@@ -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,
) )

View File

@@ -26,7 +26,7 @@
"options": { "options": {
"step": { "step": {
"init": { "init": {
"description": "Add your cloud credentials to get additional information about your Oasis Mini", "description": "Add your cloud credentials to get additional information about your device",
"data": { "data": {
"email": "[%key:common::config_flow::data::email%]", "email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]" "password": "[%key:common::config_flow::data::password%]"
@@ -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": "Live drawing"
}
}
}
} }
} }

View File

@@ -26,7 +26,7 @@
"options": { "options": {
"step": { "step": {
"init": { "init": {
"description": "Add your cloud credentials to get additional information about your Oasis Mini", "description": "Add your cloud credentials to get additional information about your device",
"data": { "data": {
"email": "Email", "email": "Email",
"password": "Password" "password": "Password"
@@ -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": "Live drawing"
}
}
}
} }
} }

View File

@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
from datetime import timedelta from datetime import timedelta
import logging
from typing import Any from typing import Any
from homeassistant.components.update import ( from homeassistant.components.update import (
@@ -19,6 +20,8 @@ from .const import DOMAIN
from .coordinator import OasisMiniCoordinator from .coordinator import OasisMiniCoordinator
from .entity import OasisMiniEntity from .entity import OasisMiniEntity
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(hours=6) SCAN_INTERVAL = timedelta(hours=6)
@@ -75,6 +78,9 @@ class OasisMiniUpdateEntity(OasisMiniEntity, UpdateEntity):
"""Update the entity.""" """Update the entity."""
await self.device.async_get_software_version() await self.device.async_get_software_version()
software = await self.device.async_cloud_get_latest_software_details() software = await self.device.async_cloud_get_latest_software_details()
if not software:
_LOGGER.warning("Unable to get latest software details")
return
self._attr_latest_version = software["version"] self._attr_latest_version = software["version"]
self._attr_release_summary = software["description"] self._attr_release_summary = software["description"]
self._attr_release_url = f"https://app.grounded.so/software/{software['id']}" self._attr_release_url = f"https://app.grounded.so/software/{software['id']}"