From 0b2c9038ef8f5766ce288afeadacf353350dfecf Mon Sep 17 00:00:00 2001 From: Alex X Date: Tue, 16 Jan 2024 09:20:01 +0300 Subject: [PATCH] Add new event yandex_scenario --- .../yandex_station/core/yandex_quasar.py | 61 ++++++++++++++++--- .../yandex_station/core/yandex_station.py | 15 +++-- tests/test_misc.py | 11 ++++ 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/custom_components/yandex_station/core/yandex_quasar.py b/custom_components/yandex_station/core/yandex_quasar.py index 4ed9eb7..640e9e6 100644 --- a/custom_components/yandex_station/core/yandex_quasar.py +++ b/custom_components/yandex_station/core/yandex_quasar.py @@ -1,6 +1,7 @@ import asyncio import json import logging +from datetime import datetime from typing import Optional from aiohttp import WSMsgType @@ -423,14 +424,60 @@ async def connect(self): break resp = msg.json() # "ping", "update_scenario_list" - if resp.get("operation") != "update_states": - continue - try: - resp = json.loads(resp["message"]) - for device in resp["updated_devices"]: + operation = resp.get("operation") + if operation == "update_states": + try: + resp = json.loads(resp["message"]) + for device in resp["updated_devices"]: + self.dispatch_update(device["id"], device) + except Exception as e: + _LOGGER.debug(f"Parse quasar update error: {msg.data}", exc_info=e) + + elif operation == "update_scenario_list": + if '"source":"create_scenario_launch"' in resp["message"]: + asyncio.create_task(self.get_voice_trigger(1)) + + async def get_voice_trigger(self, retries: int = 0): + try: + # 1. Get all scenarios history + r = await self.session.get( + "https://iot.quasar.yandex.ru/m/user/scenarios/history" + ) + raw = await r.json() + + # 2. Search latest scenario with voice trigger + scenario = next( + s + for s in raw["scenarios"] + if s["trigger_type"] == "scenario.trigger.voice" + ) + + # 3. Check if scenario too old + d1 = datetime.strptime(r.headers["Date"], "%a, %d %b %Y %H:%M:%S %Z") + d2 = datetime.strptime(scenario["launch_time"], "%Y-%m-%dT%H:%M:%SZ") + dt = (d1 - d2).total_seconds() + if dt > 5: + # try to get history once more + if retries: + await self.get_voice_trigger(retries - 1) + return + + # 4. Get speakers from launch devices + r = await self.session.get( + f"https://iot.quasar.yandex.ru/m/v3/user/launches/{scenario['id']}/edit" + ) + raw = await r.json() + + for step in raw["launch"]["steps"]: + for device in step["parameters"]["launch_devices"]: + # 5. Check if speaker device + if "quasar_info" not in device: + continue + device["scenario_name"] = raw["launch"]["name"] self.dispatch_update(device["id"], device) - except Exception as e: - _LOGGER.debug(f"Parse quasar update error: {msg.data}", exc_info=e) + + except Exception as e: + _LOGGER.debug("Can't get voice scenario", exc_info=e) async def run_forever(self): while not self.session.session.closed: diff --git a/custom_components/yandex_station/core/yandex_station.py b/custom_components/yandex_station/core/yandex_station.py index d3eaaaf..47bb095 100644 --- a/custom_components/yandex_station/core/yandex_station.py +++ b/custom_components/yandex_station/core/yandex_station.py @@ -231,14 +231,19 @@ def on_update(self, device: dict): return for item in device["capabilities"]: - if item["type"] != "devices.capabilities.quasar.server_action": - continue - event_data = item["state"] - if not event_data: + if not (event_data := item["state"]): continue + event_data["entity_id"] = self.entity_id event_data["name"] = self.name - self.hass.bus.async_fire("yandex_speaker", event_data) + + if "scenario_name" in device: + event_data["scenario_name"] = device["scenario_name"] + self.debug(f"yandex_scenario: {event_data}") + self.hass.bus.async_fire("yandex_scenario", event_data) + else: + self.debug(f"yandex_speaker: {event_data}") + self.hass.bus.async_fire("yandex_speaker", event_data) # ADDITIONAL CLASS FUNCTION diff --git a/tests/test_misc.py b/tests/test_misc.py index 91ce4b0..aba0dfe 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,3 +1,5 @@ +from datetime import datetime + from homeassistant.components import media_source from custom_components.yandex_station.core import utils @@ -23,3 +25,12 @@ def test_media_source(): media_id = media_source.generate_media_source_id("tts", id1 + id2) assert utils.decode_media_source(media_id) == query1 | query2 + + +def test_parse_date(): + s1 = "Tue, 16 Jan 2024 04:57:06 GMT" # RFC 822 + s2 = "2024-01-16T04:57:05Z" # RFC 3339 + d1 = datetime.strptime(s1, "%a, %d %b %Y %H:%M:%S %Z") + d2 = datetime.strptime(s2, "%Y-%m-%dT%H:%M:%SZ") + dt = (d1 - d2).total_seconds() + assert dt < 5