Metadata-Version: 2.4 Name: yoto_api Version: 3.1.6 Summary: A python package that makes it a bit easier to work with the yoto play API. Not associated with Yoto in any way. Home-page: https://github.com/cdnninja/yoto_api Author: cdnninja Author-email: License: MIT license Keywords: yoto_api Classifier: Development Status :: 2 - Pre-Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Requires-Python: >=3.10 Description-Content-Type: text/markdown License-File: LICENSE License-File: AUTHORS.rst Requires-Dist: aiohttp Requires-Dist: aiomqtt Dynamic: author Dynamic: classifier Dynamic: description Dynamic: description-content-type Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: requires-dist Dynamic: requires-python Dynamic: summary # yoto_api Async Python wrapper for the Yoto API: control players, browse the card library, react to live MQTT playback events. Get a client ID at . ## Credit Thanks to @buzzeddesign for help sniffing the API and @fuatakgun for the original v2.x architecture (based on kia_uvo). Credit to piitaya for version 3. ## Quick start ```python import asyncio from yoto_api import YotoClient async def main(): async with YotoClient(client_id="your_client_id") as client: auth = await client.device_code_flow_start() print(auth["verification_uri_complete"]) await client.device_code_flow_complete(auth) await client.refresh() for pid, player in client.players.items(): print(pid, player.device.name, player.model) async def on_update(player): print(player.last_event.playback_status, player.status.battery_level_percentage) await client.connect_events(list(client.players), on_update=on_update) await client.pause(next(iter(client.players))) await asyncio.sleep(60) await client.disconnect_events() asyncio.run(main()) ``` If you already have a refresh token: ```python async with YotoClient(client_id="your_client_id") as client: client.set_refresh_token(refresh_token) await client.refresh() ``` For consumers managing OAuth + session externally (e.g. HA core): ```python client = YotoClient(session=my_aiohttp_session) client.token = Token(access_token=..., refresh_token=..., ...) # caller owns the session — won't be closed by client.close() ``` ## Data model `YotoPlayer` aggregates four typed sub-objects, one per data source: - `player.device` (`Device`): immutable identity from `/devices/mine`. - `player.info` (`PlayerInfo`): settings, mac, firmware from `/config`. - `player.status` (`PlayerStatus`): runtime telemetry (battery, wifi, charging, online). - `player.last_event` (`PlaybackEvent`): live playback state pushed via MQTT (track, position, volume). All four are always present (default-initialised). The `*_refreshed_at` / `last_event_received_at` timestamps tell you whether data has actually been received. ## Common methods All public methods are async. Refresh: ```python await client.update_player_list() # /devices/mine await client.update_player_info(device_id) # /config — info + info.config await client.update_player_status(device_id) await client.update_library() await client.refresh() # list + all info ``` MQTT: ```python await client.connect_events(player_ids, on_update=cb, on_disconnect=cb) await client.subscribe_player_events(device_id) await client.unsubscribe_player_events(device_id) client.is_mqtt_connected await client.reconnect_events() await client.disconnect_events() ``` Callbacks may be sync or async. Player commands (MQTT, ~50 ms): ```python await client.play_card(player_id, "card_id", chapter_key="01", track_key="01") await client.pause(player_id) await client.resume(player_id) await client.stop(player_id) await client.set_volume(player_id, 50) # 0-100 await client.set_sleep_timer(player_id, 600) # seconds await client.set_ambients(player_id, 255, 0, 0) # RGB await client.next_track(player_id) await client.previous_track(player_id) await client.seek(player_id, position=30) ``` Settings (REST PUT): ```python import datetime await client.set_player_config( player_id, day_time=datetime.time(7, 30), night_max_volume_limit=8, day_ambient_colour="#40bfd9", repeat_all=True, day_display_brightness_auto=True, # or day_display_brightness=80 ) await client.set_alarms(player_id, alarms=[...]) await client.set_alarm_enabled(player_id, index=0, enabled=False) ``` JWT helpers (no API call): ```python from yoto_api import get_account_id, has_scope account_id = get_account_id(client.token.access_token) can_status = has_scope(client.token.access_token, "family:device-status:view") ``` ## Errors All failures raise a subclass of `YotoError`: ```python from yoto_api import YotoError, AuthenticationError, YotoAPIError, YotoMQTTError try: await client.refresh() except AuthenticationError: # token expired or invalid ... except YotoAPIError as err: # HTTP / parse error (err.status_code on 4xx/5xx) ... except YotoMQTTError: # MQTT broker / aiomqtt error ... except YotoError: # catch-all ... ``` ## Migration from 2.x See [MIGRATION_3.md](MIGRATION_3.md). Short version: `YotoManager` → `YotoClient`, flat fields on `YotoPlayer` → sub-objects, and every method is now async. ## Development ```bash pip install -r requirements.txt -r requirements_dev.txt python -m pytest tests/ # unit, no creds ``` End-to-end tests need a `.env` at the repo root: ```bash YOTO_CLIENT_ID=your_client_id YOTO_REFRESH_TOKEN=optional_refresh_token ``` Then: ```bash python -m pytest tests/e2e -m e2e -s ``` The first run prompts for a verification URL and writes the new refresh token back to `.env`. `-s` keeps the prompt visible. E2E tests are read-only and opt-in (`-m e2e`). Scripts: ```bash python scripts/check_unmapped.py # list API/MQTT keys we don't parse python scripts/debug.py # rich TUI: pick a device, watch live state python scripts/probe_mqtt.py # 30s MQTT capture → mqtt_probe.log ``` ### MQTT vs REST notes - `data/events` is pushed in real time. Subscribe and react. - `data/status` is **never** pushed spontaneously. The firmware responds to MQTT `command/status/request` within ~150ms. The REST `POST /command/status` is acked but doesn't trigger an MQTT push — use `client.request_status_push` (which routes through MQTT). - `data/status` is a subset of REST `device.status`: `powerSrc`, `wifiStrength`, `ssid`, `temp`, `upTime`, `utcTime`, `utcOffset`, `totalDisk` are REST-only. Poll `client.update_player_status()` on a slower timer for those. ## Other notes Not affiliated with Yoto Play in any way.