YandexStation icon indicating copy to clipboard operation
YandexStation copied to clipboard

Проблема с авторизацией после обновления 2023.3

Open TolianIPB opened this issue 1 year ago • 27 comments

Проблема с авторизацией после обновления 2023.3

Home Assistant 2023.3.1 Yandex Station v3.12.0

Не получается авторизоваться. У меня приложения Я.Ключ. Настройки авторизации аккаунта - логин и пароль, также включена авторизация по QR коду.

Пробовал регистрировать интеграцию разными способами. Логин пароль - Авторизация по паролю невозможна. Если выбрать Код - Неверный пароль Авторизация по Куки - выдаёт ошибку "Unknown error occurred". Авторизация по почте - выдаёт ошибку как на картинке. Авторизация по QR коду - выдаёт ошибку как на картинке.

Делал полное удаление с перезагрузкой - не помогло.

Logger: custom_components.yandex_smart_home.notifier

Source: custom_components/yandex_smart_home/notifier.py:137
Integration: Yandex Smart Home (documentation, issues)
First occurred: 12:30:16 (1 occurrences)
Last logged: 12:30:16

Failed to send state notification: [401] b'Unauthorized\n

Ya2

TolianIPB avatar Mar 03 '23 10:03 TolianIPB

Обновился. Yandex Station v3.12.1

Аналогично, при попытке авторизоваться по QR коду появляется картинка:

Ya2

И интеграция не добавляется.

У меня приложения Я.Ключ. Настройки авторизации аккаунта - логин и пароль, также включена авторизация по QR коду.

TolianIPB avatar Mar 03 '23 19:03 TolianIPB

Присоединюсь, такая же фигня(

dimonsustr avatar Mar 05 '23 07:03 dimonsustr

И у меня также Retrying setup: too many values to unpack (expected 2)

Откатился на 2023.2 - все заработало

alex-rb338 avatar Mar 05 '23 08:03 alex-rb338

+1 Retrying setup: too many values to unpack (expected 2)

dreem2001 avatar Mar 05 '23 13:03 dreem2001

YS: 3.12.1 HA: 2023.3.1 Works well!

dreem2001 avatar Mar 06 '23 06:03 dreem2001

Удалял интеграцию, перезагружал. Устанавливал, перезагружал. При авторизации через QR код вылетает картинка:

Ya2

Но сама интеграция в списке не появляется. Пожалуйста, скажите хоть куда копать...

TolianIPB avatar Mar 07 '23 13:03 TolianIPB

В логи копать. Всегда первый шаг искать ошибки в логах

AlexxIT avatar Mar 07 '23 13:03 AlexxIT

В том то всё и дело, что в попытках авторизоваться через QR код в логах сервера ничего не появляется нового.

TolianIPB avatar Mar 07 '23 16:03 TolianIPB

как я решил проблему: удалил в hacs интеграцию яндекс.станции (хотя стояла последняя 3.12.1), установил ее заново, перезагрузил HA, в id.yandex.ru/security/enter-methods поменял обычный пароль на я-ключ, и в приложении его получил. Установил интеграцию используя qr-код. До этих манипуляций я-ключ был, удалил и поставил заново.

dimonsustr avatar Mar 07 '23 18:03 dimonsustr

Это не решение проблемы. Это костыли. У меня на Яндекс ПАРОЛЬ завязано 100500 сервисов и не только в HA. Мне кажется Яндекс ведут себя неадекватно. Короче, история у меня такая. У меня работала интеграция Yandex.Station и было всё хорошо. После обновления 2023.3 у меня началось: too many values to unpack (expected 2) Удалял, ставил, но всё равно не авторизовывалось ни по паролю, ни по то почте. Тогда то я и решил попробовать QR код, хотя этот способ вынуждал меня на забитый телефон ставить ещё одно ненужное гавно-приложение. В процессе установки ЯКлюч спросил меня, не хочу ли выйти из всех аккаунтов залогиненых. Я, естественно, ответил "НЕТ!!!". Но в конце установки это чудо-приложение мне написало, что авторизация по паролю более недоступна и всё же вышла отовсюду где я был залогинен. (#@&%!) Интеграция Yandex.Station успешно залогинилась, но зато отвалилось всё остальное. Как я писал выше, у меня на ПАРОЛЬ завязано 100500 сервисов и не только в HA. Самым унизительным было заново подключать и настраивать ПЯТЬ колонок с Алисой ибо они тоже перестали авторизовываться. Чтобы дальше использовать пароль в настройках гавно-приложения Я(#@&%!)Ключ выставил возможность авторизовываться как по паролю, так и по ключу. Выставил тот же пароль, что у меня был. Как уже выяснилось потом, после восстановления авторизации по паролю и установки того же самого пароля Я(#@&%!*)Ключ сбросило мне ещё и Токен. Я получил второй круг ада по повторной настройке всего и вся для подключения к службам ТЫндекса. Сейчас всё заработало как и раньше, кроме интеграции Yandex.Station. Лично я подозреваю, что у Яндекса авторизация "Только по QR коду" и "По QR коду и по паролю" чем-то отличается в API и по этому меня приложение не пускает. Токен сменился. Куку и по почте - не проходит. У меня сейчас настроена авторизация "По QR коду и по паролю". Я подозреваю, что если я настрою "Только по QR коду", то всё заработает. Но тогда у меня отвалится куча всего остального. Для меня это не вариант. Кроме как ждать пока разработчик интеграции пофиксит авторизацию мне остаётся только наслаждаться возможностью пофлудить про HA в русскоязычном комьюнити и отвести душу :-)

TolianIPB avatar Mar 08 '23 06:03 TolianIPB

Прописал в configuration.yaml свои данные yandex_station: username: <login>@yande.ru password: <password> И снова интеграция не появилась и в логах пусто.

TolianIPB avatar Mar 08 '23 09:03 TolianIPB

+1 Retrying setup: too many values to unpack (expected 2)

maxvanceffer avatar Mar 09 '23 10:03 maxvanceffer

Плюсую. Y.S v3.11.0 H.A 2023.3.2

Имеется две Я.Станции мини. Одна перестала отображать обновление карточки 3 дня назад. HA версию, при этом, не трогал. Вторая Я.С. обновляла свою карточку на дашборде и управлялась с него. Обновил версию HA - отвалились обе.

В интеграциях: Повторная настройка: too many values to unpack (expected 2)

Shwanapacka avatar Mar 09 '23 11:03 Shwanapacka

Имеется две Я.Станции мини. Одна перестала отображать обновление карточки 3 дня назад. HA версию, при этом, не трогал. Вторая Я.С. обновляла свою карточку на дашборде и управлялась с него. Обновил версию HA - отвалились обе.

Скорее всего у тебя Токен сменился. Я вот как раз через это прошёл. Не сбрасывая и не удаляя станции в Яндекс приложении настроить их снова. Если у тебя интеграция Yandex Smart подключена через облако, то нужно отключить, поменять ключ и подключить снова. Если же Yandex Smart подключён локально, то нужно отключить привязку НА САЙТЕ ЯНДЕКСА, поменять ключ и снова подключить. В Яндекс приложении для умного дома ничего делать не нужно. В HS-е только прописать новый Токен в конфиге и рестартануть. Больше ничего делать не нужно, а то можно потерять привязку и настройку устройств.

TolianIPB avatar Mar 09 '23 12:03 TolianIPB

Короче, тайна сисек раскрыта. Яндекс что-то изменил в авторизации по паролю и теперь активно навязывает авторизацию через приложение по одноразовому паролю или QR коду. Когда вы первый раз ставите приложение Я(#@&%!)Ключ (простите за #@&%! но иначе не скажешь), не зависимо от того, что вы нажимаете оно выходит из всех приложений, запрещает авторизацию по паролю и меняет токен. Если после этого выбрать авторизацию по паролю + по QR коду, то авторизация по QR коду перестаёт работать. Есть подозрение, что Я(#@&%!)Ключ снова меняет токен, но это не точно. После этого авторизация работает только по паролю, даже если в приложении Я(#@&%!)Ключ выставлена настройка Пароль + QR. Если Вы удаляете Я(#@&%!)Ключ и ставите снова, выбирая восстановиться из облачного хранилища, то всё равно QR не работает, скорее всего потому, что токен уже другой. Если при повторной установке Я(#@&%!)Ключ пройти всё заново, то уже токен не меняется и с устройств не разлогинивается. Единственный минус - вы теряете возможность авторизовываться по паролю. Если снова выбрать авторизацию Пароль + QR в настройках приложения Я(#@&%!)Ключ, то поменяется токен и снова не будет работать QR авторизация. Вот такая вот #@&%!. Т.е. по факту остаётся два варианта - авторизация по паролю, которая уже нигде не работает по API или QR код без пароля с завязкой на чудо-приложение Я(#@&%!)Ключ. По факту Яндекс всем навязал авторизацию по своему приложения ибо #@&%! так надо. И нефиг авторизовывается по API без привязки к смартфону. (Бесит такой подход, даже Google куда адекватнее в этом).

В качестве альтернативы для разработчиков интеграции могу предложить внедрить способ авторизации по ссылке: https://yandex.ru/activate Это единственное, что ещё работает по API кроме QR. Работает у тех и только у тех, кто не настраивал QR.

Не смотря на то, что в приложении Я(#@&%!)Ключ и в профиле Тындекса есть настройка авторизации "Пароль + QR", по факту она не работает. Либо QR и навязанная двухфакторная авторизация, либо пароль. Вместе - никак.

Занавес! Тему можно закрывать.

P.S. Спасибо Тындексу и разработчикам приложения Я(#@&%!)Ключ за потерянные часы жизни!

TolianIPB avatar Mar 10 '23 08:03 TolianIPB

Я обновил Core (2023.3.3), следом интеграции: Yandex Smart Home, Yandex.Station всё встало на свои места :)

Shwanapacka avatar Mar 12 '23 19:03 Shwanapacka

Короче, тайна сисек раскрыта. Яндекс что-то изменил в авторизации по паролю и теперь активно навязывает авторизацию через приложение по одноразовому паролю или QR коду. Когда вы первый раз ставите приложение Я(#@&%!)Ключ (простите за #@&%! но иначе не скажешь), не зависимо от того, что вы нажимаете оно выходит из всех приложений, запрещает авторизацию по паролю и меняет токен. Если после этого выбрать авторизацию по паролю + по QR коду, то авторизация по QR коду перестаёт работать. Есть подозрение, что Я(#@&%!)Ключ снова меняет токен, но это не точно. После этого авторизация работает только по паролю, даже если в приложении Я(#@&%!)Ключ выставлена настройка Пароль + QR. Если Вы удаляете Я(#@&%!)Ключ и ставите снова, выбирая восстановиться из облачного хранилища, то всё равно QR не работает, скорее всего потому, что токен уже другой. Если при повторной установке Я(#@&%!)Ключ пройти всё заново, то уже токен не меняется и с устройств не разлогинивается. Единственный минус - вы теряете возможность авторизовываться по паролю. Если снова выбрать авторизацию Пароль + QR в настройках приложения Я(#@&%!)Ключ, то поменяется токен и снова не будет работать QR авторизация. Вот такая вот #@&%!. Т.е. по факту остаётся два варианта - авторизация по паролю, которая уже нигде не работает по API или QR код без пароля с завязкой на чудо-приложение Я(#@&%!)Ключ. По факту Яндекс всем навязал авторизацию по своему приложения ибо #@&%! так надо. И нефиг авторизовывается по API без привязки к смартфону. (Бесит такой подход, даже Google куда адекватнее в этом).

В качестве альтернативы для разработчиков интеграции могу предложить внедрить способ авторизации по ссылке: https://yandex.ru/activate Это единственное, что ещё работает по API кроме QR. Работает у тех и только у тех, кто не настраивал QR.

Не смотря на то, что в приложении Я(#@&%!)Ключ и в профиле Тындекса есть настройка авторизации "Пароль + QR", по факту она не работает. Либо QR и навязанная двухфакторная авторизация, либо пароль. Вместе - никак.

Занавес! Тему можно закрывать.

P.S. Спасибо Тындексу и разработчикам приложения Я(#@&%!)Ключ за потерянные часы жизни!

Так работать стало или нет?

KirMozor avatar Apr 04 '23 09:04 KirMozor

Помогите разобраться - пропала яндекс станция из интеграции Yandex.Station -самой станции нет есть только эквалайзер, в сети станция есть . Переустанавливал - не помогает, что может быть?

mail727victor avatar Apr 30 '23 03:04 mail727victor

Не знаю как с авторизацией в Яндекс станции но я смог сделать авторизацию с получением токена. Можете попробовать использовать чтобы получить токен: https://gitlab.com/KirMozor/YandexMusicApi/-/blob/main/Src/Token.cs

KirMozor avatar May 02 '23 04:05 KirMozor

Сущности Yandex станций пропали после обновлений HA с версии 2023.3. Обновления интеграции до версии 3,12,2 не принесли результата. Появляется только сущность эквалайзер. Колонки: 2 Станции Мини 2 Яндекс с дисплеем и Станция Макс Яндекс. Если заменить оригинальный yandex_quasar.py версии 3,12,2 на другой (код ниже), то сущности появляются, но TTS работает только на Станция Макс Яндекс.

Установлено: Яндекс.Станция для Home Assistant 3.12.2 Home Assistant 2023.4.6

В логах после инициализации колонок и отправки команды TTS " test":

2023-05-18 11:57:34.871 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_quasar] Получение списка устройств.
2023-05-18 11:57:37.959 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_quasar] Start quasar updates connection
2023-05-18 11:57:41.967 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_session] Обновление CSRF-токена, proxy: None
2023-05-18 11:57:43.708 WARNING (MainThread) [custom_components.yandex_station.core.utils] Can't get media_players
File "/config/custom_components/yandex_station/core/utils.py", line 378, in get_media_players
2023-05-18 11:57:43.772 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Макс 0f81 | Локальное подключение
2023-05-18 11:57:43.772 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Макс 0f81 | Обновление токена устройства
2023-05-18 11:57:43.772 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_session] Get music token
2023-05-18 11:57:43.970 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Вход | Установка кастомной иконки: yandex:station-mini-2
2023-05-18 11:57:44.088 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Кухня | Установка кастомной иконки: yandex:station-mini-2
2023-05-18 11:57:44.088 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Макс 0f81 | Установка кастомной иконки: yandex:station-max
2023-05-18 11:57:44.089 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Вход | Локальное подключение
2023-05-18 11:57:44.089 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Вход | Обновление токена устройства
2023-05-18 11:57:44.089 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_session] Get music token
2023-05-18 11:57:44.138 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Кухня | Локальное подключение
2023-05-18 11:57:44.138 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Кухня | Обновление токена устройства
2023-05-18 11:57:44.139 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_session] Get music token
2023-05-18 12:07:01.914 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Вход | Останавливаем локальное подключение
2023-05-18 12:07:01.914 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Кухня | Останавливаем локальное подключение
2023-05-18 12:07:01.914 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Макс 0f81 | Останавливаем локальное подключение
2023-05-18 12:07:01.915 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Вход | Возврат в облачный режим
2023-05-18 12:07:01.915 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Кухня | Возврат в облачный режим
2023-05-18 12:07:01.915 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Макс 0f81 | Возврат в облачный режим
2023-05-18 12:07:02.140 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_quasar] Получение списка устройств.
2023-05-18 12:07:02.669 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_quasar] Start quasar updates connection
2023-05-18 12:07:02.701 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_session] Обновление CSRF-токена, proxy: None
2023-05-18 12:07:03.385 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Вход | Установка кастомной иконки: yandex:station-mini-2
2023-05-18 12:07:03.386 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Кухня | Установка кастомной иконки: yandex:station-mini-2
2023-05-18 12:07:03.387 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Макс 0f81 | Установка кастомной иконки: yandex:station-max
2023-05-18 12:07:03.388 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Вход | Локальное подключение
2023-05-18 12:07:03.388 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Вход | Обновление токена устройства
2023-05-18 12:07:03.390 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Кухня | Локальное подключение
2023-05-18 12:07:03.390 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Кухня | Обновление токена устройства
2023-05-18 12:07:03.391 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Макс 0f81 | Локальное подключение
2023-05-18 12:07:03.391 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Макс 0f81 | Обновление токена устройства
2023-05-18 12:07:16.811 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Макс 0f81 | Останавливаем локальное подключение
2023-05-18 12:07:16.811 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Макс 0f81 | Возврат в облачный режим
2023-05-18 12:07:16.815 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Макс 0f81 | Установка кастомной иконки: yandex:station-max
2023-05-18 12:07:16.817 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Макс 0f81 | Локальное подключение
2023-05-18 12:07:16.817 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Макс 0f81 | Обновление токена устройства
2023-05-18 12:08:50.018 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Кухня | Останавливаем локальное подключение
2023-05-18 12:08:50.019 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Кухня | Возврат в облачный режим
2023-05-18 12:08:50.022 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Кухня | Установка кастомной иконки: yandex:station-mini-2
2023-05-18 12:08:50.024 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Кухня | Локальное подключение
2023-05-18 12:08:50.024 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Кухня | Обновление токена устройства
2023-05-18 12:09:07.468 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Вход | Останавливаем локальное подключение
2023-05-18 12:09:07.468 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Вход | Возврат в облачный режим
2023-05-18 12:09:07.472 DEBUG (MainThread) [custom_components.yandex_station.media_player] Яндекс Станция Вход | Установка кастомной иконки: yandex:station-mini-2
2023-05-18 12:09:07.476 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Вход | Локальное подключение
2023-05-18 12:09:07.476 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_glagol] Яндекс Станция Вход | Обновление токена устройства

2023-05-18 13:16:55.060 DEBUG (MainThread) [custom_components.yandex_station] Yandex say to: media_player.yandex_station_0f81
2023-05-18 13:16:55.064 DEBUG (MainThread) [custom_components.yandex_station.core.yandex_quasar] Яндекс Станция Макс 0f81 => cloud | test
2023-05-18 13:17:01.674 DEBUG (MainThread) [custom_components.yandex_station] Yandex say to: media_player.yandex_station_entrance
2023-05-18 13:17:07.130 DEBUG (MainThread) [custom_components.yandex_station] Yandex say to: media_player.yandex_station_kitchen

Рабочий вариант yandex_quasar.py

сущности появляются, но TTS работает только на Станция Макс Яндекс.

`

import asyncio
import json
import logging
from typing import Optional

from aiohttp import WSMsgType

from .yandex_session import YandexSession

_LOGGER = logging.getLogger(__name__)

IOT_TYPES = {
    "on": "devices.capabilities.on_off",
    "temperature": "devices.capabilities.range",
    "fan_speed": "devices.capabilities.mode",
    "thermostat": "devices.capabilities.mode",
    "heat": "devices.capabilities.mode",
    "volume": "devices.capabilities.range",
    "pause": "devices.capabilities.toggle",
    "mute": "devices.capabilities.toggle",
    "channel": "devices.capabilities.range",
    "input_source": "devices.capabilities.mode",
    "brightness": "devices.capabilities.range",
    "color": "devices.capabilities.color_setting",
    "work_speed": "devices.capabilities.mode",
    "humidity": "devices.capabilities.range",
    "ionization": "devices.capabilities.toggle",
    "backlight": "devices.capabilities.toggle",
    # kettle:
    "keep_warm": "devices.capabilities.toggle",
    "tea_mode": "devices.capabilities.mode",
    # don't work
    "hsv": "devices.capabilities.color_setting",
    "rgb": "devices.capabilities.color_setting",
    "temperature_k": "devices.capabilities.color_setting",
}

MASK_EN = "0123456789abcdef-"
MASK_RU = "оеаинтсрвлкмдпуяы"

URL_USER = "https://iot.quasar.yandex.ru/m/user"
URL_V3_USER = "https://iot.quasar.yandex.ru/m/v3/user"


def encode(uid: str) -> str:
    """Кодируем UID в рус. буквы. Яндекс привередливый."""
    return "ХА " + "".join([MASK_RU[MASK_EN.index(s)] for s in uid])


def decode(uid: str) -> Optional[str]:
    """Раскодируем UID из рус.букв."""
    try:
        return "".join([MASK_EN[MASK_RU.index(s)] for s in uid[3:]])
    except Exception:
        return None


class YandexQuasar:
    # all devices
    devices = None
    online_updated: asyncio.Event = None
    updates_task: asyncio.Task = None

    def __init__(self, session: YandexSession):
        self.session = session
        self.online_updated = asyncio.Event()
        self.online_updated.set()

    @property
    def hass_id(self):
        for device in self.devices:
            if device["name"] == "Yandex Intents":
                return device["id"]
        return None

    async def init(self):
        """Основная функция. Возвращает список колонок."""
        _LOGGER.debug("Получение списка устройств.")

        r = await self.session.get(f"{URL_V3_USER}/devices")
        resp = await r.json()
        assert resp["status"] == "ok", resp

        self.devices = []

        for house in resp["households"]:
            if "sharing_info" in house:
                continue
            self.devices += house["all"]

    @property
    def speakers(self):
        return [
            d
            for d in self.devices
            if d["type"].startswith("devices.types.smart_speaker.")
        ]

    @property
    def modules(self):
        # modules don't have cloud scenarios
        return [
            d
            for d in self.devices
            if "quasar_info" in d
            and d["quasar_info"]["platform"].startswith(
                ("yandexmodule", "yandex_tv", "goya")
            )
        ]

    async def load_speakers(self) -> list:
        speakers = self.speakers

        # Яндекс начали добавлять device_id и platform с полным списком
        # устройств
        # for speaker in speakers:
        #     await self.load_speaker_config(speaker)

        scenarios = await self.load_scenarios()
        for speaker in speakers:
            device_id = speaker['id']
            try:
                if device_id not in scenarios:
                    await self.add_scenario(device_id)
                    scenarios = await self.load_scenarios()

                speaker['scenario_id'] = scenarios[device_id]['id']
            except:
                print('bad')

        return speakers

    async def load_speaker_config(self, device: dict):
        """Загружаем device_id и platform для колонок. Они не приходят с полным
        списком устройств.
        """
        r = await self.session.get(f"{URL_USER}/devices/{device['id']}/configuration")
        resp = await r.json()
        assert resp["status"] == "ok", resp
        # device_id and platform
        device.update(resp["quasar_info"])

    async def load_scenarios(self) -> dict:
        """Получает список сценариев, которые мы ранее создали."""
        r = await self.session.get(f"{URL_USER}/scenarios")
        resp = await r.json()
        assert resp["status"] == "ok", resp

        return {
            decode(d["name"]): d
            for d in resp["scenarios"]
            if d["name"].startswith("ХА ")
        }

    async def add_scenario(self, device_id: str):
        """Добавляет сценарий-пустышку."""
        name = encode(device_id)
        payload = {
            "name": name,
            "icon": "home",
            "triggers": [{"type": "scenario.trigger.voice", "value": name[3:]}],
            "steps": [
                {
                    "type": "scenarios.steps.actions",
                    "parameters": {
                        "requested_speaker_capabilities": [],
                        "launch_devices": [
                            {
                                "id": device_id,
                                "capabilities": [
                                    {
                                        "type": "devices.capabilities.quasar.server_action",
                                        "state": {
                                            "instance": "phrase_action",
                                            "value": "пустышка",
                                        },
                                    }
                                ],
                            }
                        ],
                    },
                }
            ],
        }
        r = await self.session.post(f"{URL_USER}/scenarios", json=payload)
        resp = await r.json()
        if resp["status"] != "ok":
            print()
        assert resp["status"] == "ok", resp

    async def add_intent(self, name: str, text: str, num: int):
        speaker = (
            [
                {
                    "type": "devices.capabilities.quasar.server_action",
                    "state": {"instance": "phrase_action", "value": text},
                }
            ]
            if text
            else [
                {
                    "type": "devices.capabilities.quasar.server_action",
                    "state": {
                        "instance": "text_action",
                        "value": "Yandex Intents громкость 100",
                    },
                }
            ]
        )

        payload = {
            "name": name,
            "icon": "home",
            "triggers": [{"type": "scenario.trigger.voice", "value": name}],
            "steps": [
                {
                    "type": "scenarios.steps.actions",
                    "parameters": {
                        "requested_speaker_capabilities": speaker,
                        "launch_devices": [
                            {
                                "id": self.hass_id,
                                "capabilities": [
                                    {
                                        "type": "devices.capabilities.range",
                                        "state": {
                                            "instance": "volume",
                                            "relative": False,
                                            "value": num,
                                        },
                                    }
                                ],
                            }
                        ],
                    },
                }
            ],
        }
        r = await self.session.post(f"{URL_USER}/scenarios", json=payload)
        resp = await r.json()
        assert resp["status"] == "ok", resp

    async def send(self, device: dict, text: str, is_tts: bool = False):
        """Запускает сценарий на выполнение команды или TTS."""
        # skip send for yandex modules
        if "scenario_id" not in device:
            return
        _LOGGER.debug(f"{device['name']} => cloud | {text}")

        action = "phrase_action" if is_tts else "text_action"
        name = encode(device["id"])
        payload = {
            "name": name,
            "icon": "home",
            "triggers": [{"type": "scenario.trigger.voice", "value": name[3:]}],
            "steps": [
                {
                    "type": "scenarios.steps.actions",
                    "parameters": {
                        "requested_speaker_capabilities": [],
                        "launch_devices": [
                            {
                                "id": device["id"],
                                "capabilities": [
                                    {
                                        "type": "devices.capabilities.quasar.server_action",
                                        "state": {"instance": action, "value": text},
                                    }
                                ],
                            }
                        ],
                    },
                }
            ],
        }

        sid = device["scenario_id"]

        r = await self.session.put(f"{URL_USER}/scenarios/{sid}", json=payload)
        resp = await r.json()
        assert resp["status"] == "ok", resp

        r = await self.session.post(f"{URL_USER}/scenarios/{sid}/actions")
        resp = await r.json()
        assert resp["status"] == "ok", resp

    async def load_local_speakers(self):
        """Загружает список локальных колонок. Не используется."""
        try:
            r = await self.session.get("https://quasar.yandex.net/glagol/device_list")
            resp = await r.json()
            return [
                {"device_id": d["id"], "name": d["name"], "platform": d["platform"]}
                for d in resp["devices"]
            ]

        except:
            _LOGGER.exception("Load local speakers")
            return None

    async def get_device_config(self, device: dict) -> dict:
        payload = {
            "device_id": device["quasar_info"]["device_id"],
            "platform": device["quasar_info"]["platform"],
        }
        r = await self.session.get(
            "https://quasar.yandex.ru/get_device_config", params=payload
        )
        resp = await r.json()
        assert resp["status"] == "ok", resp
        return resp["config"]

    async def set_device_config(self, device: dict, device_config: dict):
        _LOGGER.debug(f"Меняем конфиг станции: {device_config}")

        payload = {
            "device_id": device["quasar_info"]["device_id"],
            "platform": device["quasar_info"]["platform"],
        }
        r = await self.session.post(
            "https://quasar.yandex.ru/set_device_config",
            params=payload,
            json=device_config,
        )
        resp = await r.json()
        assert resp["status"] == "ok", resp

    async def get_device(self, deviceid: str):
        r = await self.session.get(f"{URL_USER}/devices/{deviceid}")
        resp = await r.json()
        assert resp["status"] == "ok", resp
        return resp

    async def device_action(self, deviceid: str, **kwargs):
        _LOGGER.debug(f"Device action: {kwargs}")

        actions = []
        for k, v in kwargs.items():
            type_ = (
                "devices.capabilities.custom.button" if k.isdecimal() else IOT_TYPES[k]
            )
            state = (
                {"instance": k, "value": v, "relative": True}
                if k in ("volume", "channel")
                else {"instance": k, "value": v}
            )
            actions.append({"type": type_, "state": state})

        r = await self.session.post(
            f"{URL_USER}/devices/{deviceid}/actions", json={"actions": actions}
        )
        resp = await r.json()
        assert resp["status"] == "ok", resp

    async def update_online_stats(self):
        if not self.online_updated.is_set():
            await self.online_updated.wait()
            return

        self.online_updated.clear()

        # _LOGGER.debug(f"Update speakers online status")

        try:
            r = await self.session.get("https://quasar.yandex.ru/devices_online_stats")
            resp = await r.json()
            assert resp["status"] == "ok", resp
        except:
            return
        finally:
            self.online_updated.set()

        for speaker in resp["items"]:
            for device in self.devices:
                if (
                    "quasar_info" not in device
                    or device["quasar_info"]["device_id"] != speaker["id"]
                ):
                    continue
                device["online"] = speaker["online"]
                break

    async def _updates_connection(self, handler):
        r = await self.session.get("https://iot.quasar.yandex.ru/m/v3/user/devices")
        resp = await r.json()
        assert resp["status"] == "ok", resp

        ws = await self.session.ws_connect(resp["updates_url"], heartbeat=60)
        _LOGGER.debug("Start quasar updates connection")
        async for msg in ws:
            if msg.type != WSMsgType.TEXT:
                break
            resp = msg.json()
            # "ping", "update_scenario_list"
            if resp.get("operation") != "update_states":
                continue
            try:
                resp = json.loads(resp["message"])
                for upd in resp["updated_devices"]:
                    if not upd.get("capabilities"):
                        continue
                    for cap in upd["capabilities"]:
                        state = cap.get("state")
                        if not state:
                            continue
                        if cap["type"] == "devices.capabilities.quasar.server_action":
                            for speaker in self.speakers:
                                if speaker["id"] == upd["id"]:
                                    entity = speaker.get("entity")
                                    if not entity:
                                        break
                                    state["entity_id"] = entity.entity_id
                                    state["name"] = entity.name
                                    await handler(state)
                                    break
            except:
                _LOGGER.debug(f"Parse quasar update error: {msg.data}")

    async def _updates_loop(self, handler):
        while True:
            try:
                await self._updates_connection(handler)
            except Exception as e:
                _LOGGER.debug(f"Quasar update error: {e}")
            await asyncio.sleep(30)

    def handle_updates(self, handler):
        self.updates_task = asyncio.create_task(self._updates_loop(handler))

    def stop(self):
        if self.updates_task:
            self.updates_task.cancel()

    async def set_account_config(self, key: str, value):
        kv = ACCOUNT_CONFIG.get(key)
        assert kv and value in kv["values"], f"{key}={value}"

        if kv.get("api") == "user/settings":
            # https://iot.quasar.yandex.ru/m/user/settings
            r = await self.session.post(
                URL_USER + "/settings", json={kv["key"]: kv["values"][value]}
            )

        else:
            r = await self.session.get("https://quasar.yandex.ru/get_account_config")
            resp = await r.json()
            assert resp["status"] == "ok", resp

            payload: dict = resp["config"]
            payload[kv["key"]] = kv["values"][value]

            r = await self.session.post(
                "https://quasar.yandex.ru/set_account_config", json=payload
            )

        resp = await r.json()
        assert resp["status"] == "ok", resp


BOOL_CONFIG = {"да": True, "нет": False}
ACCOUNT_CONFIG = {
    "без лишних слов": {
        "api": "user/settings",
        "key": "iot",
        "values": {
            "да": {"response_reaction_type": "sound"},
            "нет": {"response_reaction_type": "nlg"},
        },
    },
    "ответить шепотом": {
        "api": "user/settings",
        "key": "tts_whisper",
        "values": BOOL_CONFIG,
    },
    "анонсировать треки": {
        "api": "user/settings",
        "key": "music",
        "values": {
            "да": {"announce_tracks": True},
            "нет": {"announce_tracks": False},
        },
    },
    "скрывать названия товаров": {
        "api": "user/settings",
        "key": "order",
        "values": {
            "да": {"hide_item_names": True},
            "нет": {"hide_item_names": False},
        },
    },
    "звук активации": {"key": "jingle", "values": BOOL_CONFIG},  # /get_account_config
    "одним устройством": {
        "key": "smartActivation",  # /get_account_config
        "values": BOOL_CONFIG,
    },
    "понимать детей": {
        "key": "useBiometryChildScoring",  # /get_account_config
        "values": BOOL_CONFIG,
    },
    "рассказывать о навыках": {
        "key": "aliceProactivity",  # /get_account_config
        "values": BOOL_CONFIG,
    },
    "взрослый голос": {
        "key": "contentAccess",  # /get_account_config
        "values": {
            "умеренный": "medium",
            "семейный": "children",
            "безопасный": "safe",
            "без ограничений": "without",
        },
    },
    "детский голос": {
        "key": "childContentAccess",  # /get_account_config
        "values": {
            "безопасный": "safe",
            "семейный": "children",
        },
    },
    "имя": {
        "key": "spotter",  # /get_account_config
        "values": {
            "алиса": "alisa",
            "яндекс": "yandex",
        },
    },
}

`

trosnyakS avatar May 18 '23 10:05 trosnyakS

Сущности Yandex станций пропали после обновлений HA с версии 2023.3. Обновления интеграции до версии 3,12,2 не принесли результата. Появляется только сущность эквалайзер. Колонки: 2 Станции Мини 2 Яндекс с дисплеем и Станция Макс Яндекс. Если заменить оригинальный yandex_quasar.py версии 3,12,2 на другой (код ниже), то сущности появляются, но TTS работает только на Станция Макс Яндекс. `

У меня тоже пропали сущности станций после обновления до 2023.5.4. Воспользовался вашим файлом (взял из него только метод load_speakers) и все заработало как и раньше. Есть колонки мини с часами и лайт - на всех и tts работает и вся остальная функциональность.

Regressor avatar Jun 09 '23 05:06 Regressor

Рабочий вариант yandex_quasar.py

Впервые пытаюсь настроить интеграцию. Home Assistant была версии 7.3. Интеграция не появлялась в установленных после прохождения QR-кода, после попыток ввода одноразового ключа, Coocies тоже выдают ошибку. Обновил Home Assistant на версию 8.0. Тоже не запускается. До этого у меня стояла и стоит интеграция Yandex Smart Home. Надеюсь она не мешает. Попробовал вставить ваш код в /config/custom_components/yandex_station/core/yandex_quasar.py. Ничего не изменилось. Что делать-то?

Bagunda avatar Aug 04 '23 13:08 Bagunda

Сущности Yandex станций пропали после обновлений HA с версии 2023.3. Обновления интеграции до версии 3,12,2 не принесли результата. Появляется только сущность эквалайзер. Колонки: 2 Станции Мини 2 Яндекс с дисплеем и Станция Макс Яндекс. Если заменить оригинальный yandex_quasar.py версии 3,12,2 на другой (код ниже), то сущности появляются, но TTS работает только на Станция Макс Яндекс. `

У меня тоже пропали сущности станций после обновления до 2023.5.4. Воспользовался вашим файлом (взял из него только метод load_speakers) и все заработало как и раньше. Есть колонки мини с часами и лайт - на всех и tts работает и вся остальная функциональность.

Подтверждаю, заменил у себя только метод load_speakers и появились сущности и весь функционал как и был до того как пропал

leonaft avatar Aug 23 '23 06:08 leonaft

Добрый день! Сейчас столкнулся с подобной проблемой YS 3.12.4 не проходит авторизация а если таки проходит то не появляются сами устройства. При этом еще два сервера HA с такими же версиями софта работают без проблем. Есть какое то решение? Ну или может кто подскажет в чем причина такого поведения?

Rein-PY avatar Oct 16 '23 11:10 Rein-PY

Если сервера работают - заберите с них авторизацию и войдите по токену

AlexxIT avatar Oct 16 '23 16:10 AlexxIT

Если сервера работают - заберите с них авторизацию и войдите по токену

в home assistant токен можно найти ./.storage/core.config_entries

gvkrum avatar Mar 17 '24 21:03 gvkrum

Если сервера работают - заберите с них авторизацию и войдите по токену

КАК ЭТО МОЖНО СДЕЛАТЬ, ОБЪЯСНИТЕ ПЛИЗ? ЧТО ОТКУДА И КУДА СКОПИРОВАТЬ?

nafisnagim avatar Mar 23 '24 21:03 nafisnagim