1
0
mirror of https://github.com/natekspencer/hacs-oasis_mini.git synced 2025-12-06 18:44:14 -05:00
Files
hacs-oasis_mini/update_tracks.py
Nathan Spencer 379b6f67f2 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>
2025-11-24 01:09:23 -07:00

95 lines
3.2 KiB
Python

"""Script to update track details from Grounded Labs."""
from __future__ import annotations
import asyncio
import json
import os
from typing import Any
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:
"""
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 "Kinetic Oasis"
async def update_tracks() -> None:
"""
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:
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
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")
return
tracks = {k: v for k, v in TRACKS.items() if k in map(lambda d: d["id"], data)}
tracks.update(updated_tracks)
tracks = dict(sorted(tracks.items(), key=lambda t: t[1]["name"].lower()))
with open(
"custom_components/oasis_mini/pyoasiscontrol/tracks.json", "w", encoding="utf8"
) as file:
json.dump(tracks, file, indent=2, ensure_ascii=False)
if __name__ == "__main__":
asyncio.run(update_tracks())