mirror of
https://github.com/natekspencer/hacs-oasis_mini.git
synced 2025-12-06 18:44:14 -05:00
Swap out direct HTTP connection with server MQTT connection to handle firmware 2.60+ (#98)
* Switch to using mqtt * Better mqtt handling when connection is interrupted * Get track info from the cloud when playlist or index changes * Add additional helpers * Dynamically handle devices and other enhancements * 📝 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` * Fix formatting in transport.py * Replace tabs with spaces * Use tuples instead of sets for descriptors * Encode svg in image entity * Fix iot_class * Fix tracks list url * Ensure update_tracks closes the connection * Fix number typing and docstring * Fix docstring in update_tracks * Cache playlist based on type * Fix formatting in device.py * Add missing async_send_auto_clean_command to http client * Propagate UnauthenticatedError from async_get_track_info * Adjust exceptions * Move create_client outside of try block in config_flow * Formatting * Address PR comments * Formatting * Add noqa: ARG001 on unused hass * Close cloud/MQTT clients if initial coordinator refresh fails. * Address PR again * PR fixes * Pass config entry to coordinator * Remove async_timeout (thanks ChatGPT... not) * Address PR * Replace magic numbers for status code * Update autoplay wording/ordering --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -7,54 +7,74 @@ import json
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from custom_components.oasis_mini.pyoasismini import OasisMini
|
||||
from custom_components.oasis_mini.pyoasismini.const import TRACKS
|
||||
from custom_components.oasis_mini.pyoasiscontrol import OasisCloudClient
|
||||
from custom_components.oasis_mini.pyoasiscontrol.const import TRACKS
|
||||
|
||||
ACCESS_TOKEN = os.getenv("GROUNDED_TOKEN")
|
||||
|
||||
|
||||
def get_author_name(data: dict) -> str:
|
||||
"""Get author name from a dict."""
|
||||
"""
|
||||
Extracts the author's display name from a nested track data dictionary.
|
||||
|
||||
Parameters:
|
||||
data (dict): A mapping representing track/result data. Expected shape is
|
||||
{"author": {"user": {"name": ..., "nickname": ...}}}.
|
||||
|
||||
Returns:
|
||||
str: The author's `name` if present, otherwise the author's `nickname`, otherwise "Kinetic Oasis".
|
||||
"""
|
||||
author = (data.get("author") or {}).get("user") or {}
|
||||
return author.get("name") or author.get("nickname") or "Oasis Mini"
|
||||
return author.get("name") or author.get("nickname") or "Kinetic Oasis"
|
||||
|
||||
|
||||
async def update_tracks() -> None:
|
||||
"""Update tracks."""
|
||||
client = OasisMini("", ACCESS_TOKEN)
|
||||
"""
|
||||
Fetch tracks from the Grounded Labs cloud, detect new or changed public tracks
|
||||
compared to the local TRACKS mapping, augment changed entries with author and
|
||||
reduced SVG content, and persist the merged, sorted track list to
|
||||
`custom_components/oasis_mini/pyoasiscontrol/tracks.json`.
|
||||
|
||||
Side effects:
|
||||
- May print error or status messages to stdout.
|
||||
- Writes the updated tracks JSON file.
|
||||
- Ensures the OasisCloudClient session is closed and returns early on errors or
|
||||
unexpected data.
|
||||
"""
|
||||
client = OasisCloudClient(access_token=ACCESS_TOKEN)
|
||||
try:
|
||||
data = await client.async_cloud_get_tracks()
|
||||
except Exception as ex:
|
||||
print(type(ex).__name__, ex)
|
||||
await client.session.close()
|
||||
return
|
||||
try:
|
||||
data = await client.async_get_tracks()
|
||||
except Exception as ex:
|
||||
print(type(ex).__name__, ex)
|
||||
return
|
||||
|
||||
if not isinstance(data, list):
|
||||
print("Unexpected result:", data)
|
||||
return
|
||||
if not isinstance(data, list):
|
||||
print("Unexpected result:", data)
|
||||
return
|
||||
|
||||
updated_tracks: dict[int, dict[str, Any]] = {}
|
||||
for result in filter(lambda d: d["public"], data):
|
||||
if (
|
||||
(track_id := result["id"]) not in TRACKS
|
||||
or any(
|
||||
result[field] != TRACKS[track_id].get(field)
|
||||
for field in ("name", "image", "png_image")
|
||||
)
|
||||
or TRACKS[track_id].get("author") != get_author_name(result)
|
||||
):
|
||||
print(f"Updating track {track_id}: {result['name']}")
|
||||
track_info = await client.async_cloud_get_track_info(int(track_id))
|
||||
if not track_info:
|
||||
print("No track info")
|
||||
break
|
||||
result["author"] = get_author_name(result)
|
||||
result["reduced_svg_content_new"] = track_info.get(
|
||||
"reduced_svg_content_new"
|
||||
)
|
||||
updated_tracks[track_id] = result
|
||||
await client.session.close()
|
||||
updated_tracks: dict[int, dict[str, Any]] = {}
|
||||
for result in filter(lambda d: d["public"], data):
|
||||
if (
|
||||
(track_id := result["id"]) not in TRACKS
|
||||
or any(
|
||||
result[field] != TRACKS[track_id].get(field)
|
||||
for field in ("name", "image", "png_image")
|
||||
)
|
||||
or TRACKS[track_id].get("author") != get_author_name(result)
|
||||
):
|
||||
print(f"Updating track {track_id}: {result['name']}")
|
||||
track_info = await client.async_get_track_info(int(track_id))
|
||||
if not track_info:
|
||||
print("No track info")
|
||||
break
|
||||
result["author"] = get_author_name(result)
|
||||
result["reduced_svg_content_new"] = track_info.get(
|
||||
"reduced_svg_content_new"
|
||||
)
|
||||
updated_tracks[track_id] = result
|
||||
finally:
|
||||
await client.async_close()
|
||||
|
||||
if not updated_tracks:
|
||||
print("No updated tracks")
|
||||
@@ -65,7 +85,7 @@ async def update_tracks() -> None:
|
||||
tracks = dict(sorted(tracks.items(), key=lambda t: t[1]["name"].lower()))
|
||||
|
||||
with open(
|
||||
"custom_components/oasis_mini/pyoasismini/tracks.json", "w", encoding="utf8"
|
||||
"custom_components/oasis_mini/pyoasiscontrol/tracks.json", "w", encoding="utf8"
|
||||
) as file:
|
||||
json.dump(tracks, file, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user