1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-12-06 18:44:14 -05:00

30 Commits

Author SHA1 Message Date
Nathan Spencer
b32199c334 Pass config entry to coordinator 2025-11-24 06:03:50 +00:00
Nathan Spencer
5c49119ae5 PR fixes 2025-11-24 05:51:55 +00:00
Nathan Spencer
fbb3012379 Address PR again 2025-11-24 05:39:53 +00:00
Nathan Spencer
ac005c70c2 Close cloud/MQTT clients if initial coordinator refresh fails. 2025-11-24 05:27:49 +00:00
Nathan Spencer
873d2d4bb0 Add noqa: ARG001 on unused hass 2025-11-24 05:17:26 +00:00
Nathan Spencer
04be6626a7 Formatting 2025-11-24 04:37:10 +00:00
Nathan Spencer
14223bd1c9 Address PR comments 2025-11-24 04:31:52 +00:00
Nathan Spencer
1d521bcc18 Formatting 2025-11-24 04:15:53 +00:00
Nathan Spencer
2994e73187 Move create_client outside of try block in config_flow 2025-11-24 04:08:31 +00:00
Nathan Spencer
e4f6cd2803 Adjust exceptions 2025-11-24 04:06:42 +00:00
Nathan Spencer
1cc3585653 Propagate UnauthenticatedError from async_get_track_info 2025-11-24 03:12:47 +00:00
Nathan Spencer
2f28f7c4bd Add missing async_send_auto_clean_command to http client 2025-11-24 02:22:20 +00:00
Nathan Spencer
81668c595a Fix formatting in device.py 2025-11-24 02:14:48 +00:00
Nathan Spencer
c17d1682d0 Cache playlist based on type 2025-11-24 02:06:57 +00:00
Nathan Spencer
f0669c7f63 Fix docstring in update_tracks 2025-11-24 01:41:41 +00:00
Nathan Spencer
8abfc047f9 Fix number typing and docstring 2025-11-24 01:30:17 +00:00
Nathan Spencer
0df118d18d Ensure update_tracks closes the connection 2025-11-24 01:26:46 +00:00
Nathan Spencer
0ebab392fb Fix tracks list url 2025-11-24 01:15:35 +00:00
Nathan Spencer
a15548e387 Fix iot_class 2025-11-24 01:14:52 +00:00
Nathan Spencer
b459e3eb9d Encode svg in image entity 2025-11-24 01:12:13 +00:00
Nathan Spencer
a6ecd740be Use tuples instead of sets for descriptors 2025-11-24 01:04:56 +00:00
Nathan Spencer
aa7abc2174 Merge pull request #99 from natekspencer/coderabbitai/docstrings/cf21a5d
📝 Add docstrings to `mqtt`
2025-11-23 17:54:16 -07:00
Nathan Spencer
a548ac5fe2 Replace tabs with spaces 2025-11-23 17:53:22 -07:00
Nathan Spencer
ce22238ae6 Fix formatting in transport.py 2025-11-23 17:43:10 -07:00
coderabbitai[bot]
4ef28fc741 📝 Add docstrings to mqtt
Docstrings generation was requested by @natekspencer.

* https://github.com/natekspencer/hacs-oasis_mini/pull/98#issuecomment-3568450288

The following files were modified:

* `custom_components/oasis_mini/__init__.py`
* `custom_components/oasis_mini/binary_sensor.py`
* `custom_components/oasis_mini/button.py`
* `custom_components/oasis_mini/config_flow.py`
* `custom_components/oasis_mini/coordinator.py`
* `custom_components/oasis_mini/entity.py`
* `custom_components/oasis_mini/helpers.py`
* `custom_components/oasis_mini/image.py`
* `custom_components/oasis_mini/light.py`
* `custom_components/oasis_mini/media_player.py`
* `custom_components/oasis_mini/number.py`
* `custom_components/oasis_mini/pyoasiscontrol/clients/cloud_client.py`
* `custom_components/oasis_mini/pyoasiscontrol/clients/http_client.py`
* `custom_components/oasis_mini/pyoasiscontrol/clients/mqtt_client.py`
* `custom_components/oasis_mini/pyoasiscontrol/clients/transport.py`
* `custom_components/oasis_mini/pyoasiscontrol/device.py`
* `custom_components/oasis_mini/pyoasiscontrol/utils.py`
* `custom_components/oasis_mini/select.py`
* `custom_components/oasis_mini/sensor.py`
* `custom_components/oasis_mini/switch.py`
* `custom_components/oasis_mini/update.py`
* `update_tracks.py`
2025-11-23 23:18:59 +00:00
Nathan Spencer
cf21a5d995 Dynamically handle devices and other enhancements 2025-11-23 22:49:26 +00:00
Nathan Spencer
83de1d5606 Add additional helpers 2025-11-23 06:45:01 +00:00
Nathan Spencer
2a92212aad Get track info from the cloud when playlist or index changes 2025-11-23 00:13:45 +00:00
Nathan Spencer
ecad472bbd Better mqtt handling when connection is interrupted 2025-11-22 20:51:17 +00:00
Nathan Spencer
886d7598f3 Switch to using mqtt 2025-11-22 04:40:58 +00:00
8 changed files with 60 additions and 82 deletions

View File

@@ -2,11 +2,12 @@
from __future__ import annotations
import asyncio
from datetime import datetime, timedelta
import logging
from typing import TYPE_CHECKING
import async_timeout
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -67,7 +68,7 @@ class OasisDeviceCoordinator(DataUpdateCoordinator[list[OasisDevice]]):
self.attempt += 1
try:
async with asyncio.timeout(30):
async with async_timeout.timeout(30):
raw_devices = await self.cloud_client.async_get_devices()
existing_by_serial = {

View File

@@ -6,12 +6,14 @@ import asyncio
import logging
from typing import Any
import async_timeout
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .pyoasiscontrol import OasisCloudClient, OasisDevice
from .pyoasiscontrol.const import STATUS_PLAYING, TRACKS
from .pyoasiscontrol.const import TRACKS
_LOGGER = logging.getLogger(__name__)
@@ -42,26 +44,24 @@ async def add_and_play_track(device: OasisDevice, track: int) -> None:
track (int): The track id to add and play.
Raises:
TimeoutError: If the operation does not complete within 10 seconds.
async_timeout.TimeoutError: If the operation does not complete within 10 seconds.
"""
async with asyncio.timeout(10):
async with async_timeout.timeout(10):
if track not in device.playlist:
await device.async_add_track_to_playlist(track)
# Wait for device state to reflect the newly added track
while track not in device.playlist:
await asyncio.sleep(0.1)
# Ensure the track is positioned immediately after the current track and select it
# Move track to next item in the playlist and then select it
if (index := device.playlist.index(track)) != device.playlist_index:
# Calculate the position after the current track
if index != (
_next := min(device.playlist_index + 1, len(device.playlist) - 1)
):
await device.async_move_track(index, _next)
await device.async_change_track(_next)
if device.status_code != STATUS_PLAYING:
if device.status_code != 4:
await device.async_play()

View File

@@ -23,16 +23,6 @@ from .const import DOMAIN
from .entity import OasisDeviceEntity
from .helpers import get_track_id
from .pyoasiscontrol import OasisDevice
from .pyoasiscontrol.const import (
STATUS_CENTERING,
STATUS_DOWNLOADING,
STATUS_ERROR,
STATUS_LIVE,
STATUS_PAUSED,
STATUS_PLAYING,
STATUS_STOPPED,
STATUS_UPDATING,
)
async def async_setup_entry(
@@ -140,17 +130,17 @@ class OasisDeviceMediaPlayerEntity(OasisDeviceEntity, MediaPlayerEntity):
def state(self) -> MediaPlayerState:
"""State of the player."""
status_code = self.device.status_code
if self.device.error or status_code in (STATUS_ERROR, STATUS_UPDATING):
if self.device.error or status_code in (9, 11):
return MediaPlayerState.OFF
if status_code == STATUS_STOPPED:
if status_code == 2:
return MediaPlayerState.IDLE
if status_code in (STATUS_CENTERING, STATUS_DOWNLOADING):
if status_code in (3, 13):
return MediaPlayerState.BUFFERING
if status_code == STATUS_PLAYING:
if status_code == 4:
return MediaPlayerState.PLAYING
if status_code == STATUS_PAUSED:
if status_code == 5:
return MediaPlayerState.PAUSED
if status_code == STATUS_LIVE:
if status_code == 15:
return MediaPlayerState.ON
return MediaPlayerState.IDLE

View File

@@ -12,19 +12,19 @@ try:
TRACKS: Final[dict[int, dict[str, Any]]] = {
int(k): v for k, v in json.load(file).items()
}
except (FileNotFoundError, json.JSONDecodeError, OSError):
except Exception: # ignore: broad-except
TRACKS = {}
AUTOPLAY_MAP: Final[dict[str, str]] = {
"1": "Off", # display off (disabled) first
"0": "Immediately",
"2": "After 5 minutes",
"3": "After 10 minutes",
"4": "After 30 minutes",
"6": "After 1 hour",
"7": "After 6 hours",
"8": "After 12 hours",
"5": "After 24 hours", # purposefully placed so time is incrementally displayed
"0": "on",
"1": "off",
"2": "5 minutes",
"3": "10 minutes",
"4": "30 minutes",
"6": "1 hour",
"7": "6 hours",
"8": "12 hours",
"5": "24 hours",
}
ERROR_CODE_MAP: Final[dict[int, str]] = {
@@ -94,28 +94,17 @@ LED_EFFECTS: Final[dict[str, str]] = {
"41": "Color Comets",
}
STATUS_BOOTING: Final[int] = 0
STATUS_STOPPED: Final[int] = 2
STATUS_CENTERING: Final[int] = 3
STATUS_PLAYING: Final[int] = 4
STATUS_PAUSED: Final[int] = 5
STATUS_SLEEPING: Final[int] = 6
STATUS_ERROR: Final[int] = 9
STATUS_UPDATING: Final[int] = 11
STATUS_DOWNLOADING: Final[int] = 13
STATUS_BUSY: Final[int] = 14
STATUS_LIVE: Final[int] = 15
STATUS_CODE_SLEEPING: Final = 6
STATUS_CODE_MAP: Final[dict[int, str]] = {
STATUS_BOOTING: "booting",
STATUS_STOPPED: "stopped",
STATUS_CENTERING: "centering",
STATUS_PLAYING: "playing",
STATUS_PAUSED: "paused",
STATUS_SLEEPING: "sleeping",
STATUS_ERROR: "error",
STATUS_UPDATING: "updating",
STATUS_DOWNLOADING: "downloading",
STATUS_BUSY: "busy",
STATUS_LIVE: "live",
0: "booting",
2: "stopped",
3: "centering",
4: "playing",
5: "paused",
STATUS_CODE_SLEEPING: "sleeping",
9: "error",
11: "updating",
13: "downloading",
14: "busy",
15: "live",
}

View File

@@ -10,8 +10,7 @@ from .const import (
ERROR_CODE_MAP,
LED_EFFECTS,
STATUS_CODE_MAP,
STATUS_ERROR,
STATUS_SLEEPING,
STATUS_CODE_SLEEPING,
TRACKS,
)
from .utils import _bit_to_bool, _parse_int, create_svg, decrypt_svg_content
@@ -164,7 +163,7 @@ class OasisDevice:
Returns:
`true` if the device is sleeping, `false` otherwise.
"""
return self.status_code == STATUS_SLEEPING
return self.status_code == STATUS_CODE_SLEEPING
def attach_client(self, client: OasisClientProtocol) -> None:
"""Attach a transport client (MQTT, HTTP, etc.) to this device."""
@@ -344,7 +343,7 @@ class OasisDevice:
Returns:
str: The mapped error message when the device status indicates an error (status code 9); `None` otherwise.
"""
if self.status_code == STATUS_ERROR:
if self.status_code == 9:
return ERROR_CODE_MAP.get(self.error, f"Unknown ({self.error})")
return None

View File

@@ -76,15 +76,15 @@
"autoplay": {
"name": "Autoplay",
"state": {
"1": "Off",
"0": "Immediately",
"2": "After 5 minutes",
"3": "After 10 minutes",
"4": "After 30 minutes",
"6": "After 1 hour",
"7": "After 6 hours",
"8": "After 12 hours",
"5": "After 24 hours"
"0": "on",
"1": "off",
"2": "5 minutes",
"3": "10 minutes",
"4": "30 minutes",
"6": "1 hour",
"7": "6 hours",
"8": "12 hours",
"5": "24 hours"
}
},
"playlist": {

View File

@@ -76,15 +76,15 @@
"autoplay": {
"name": "Autoplay",
"state": {
"1": "Off",
"0": "Immediately",
"2": "After 5 minutes",
"3": "After 10 minutes",
"4": "After 30 minutes",
"6": "After 1 hour",
"7": "After 6 hours",
"8": "After 12 hours",
"5": "After 24 hours"
"0": "on",
"1": "off",
"2": "5 minutes",
"3": "10 minutes",
"4": "30 minutes",
"6": "1 hour",
"7": "6 hours",
"8": "12 hours",
"5": "24 hours"
}
},
"playlist": {

View File

@@ -18,7 +18,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import OasisDeviceConfigEntry, setup_platform_from_coordinator
from .entity import OasisDeviceEntity
from .pyoasiscontrol import OasisDevice
from .pyoasiscontrol.const import STATUS_UPDATING
_LOGGER = logging.getLogger(__name__)
@@ -72,7 +71,7 @@ class OasisDeviceUpdateEntity(OasisDeviceEntity, UpdateEntity):
@property
def in_progress(self) -> bool | int:
"""Update installation progress."""
if self.device.status_code == STATUS_UPDATING:
if self.device.status_code == 11:
return self.device.download_progress
return False