Skip to content

Latest commit

 

History

History
512 lines (363 loc) · 60.6 KB

File metadata and controls

512 lines (363 loc) · 60.6 KB

Changelog

Все заметные изменения в этом проекте документируются здесь. Формат основан на Keep a Changelog, версионирование — SemVer.

[Unreleased]

Fixed

  • camera.<name>_lyrics_na_tv: артист/заголовок теперь обновляются в караоке-стриме для Bluetooth и радио. artist вычислялся один раз до while-loop и оставался frozen. Цикл живёт, пока track_id неизменен — а для BT/radio это None (либо stream-id) постоянно, при этом метаданные меняются с каждым треком. Итог: track.title брался свежий из coordinator.track и обновлялся, а artist застревал. Перенёс вычисление внутрь loop после track = self.coordinator.track (commit 4577285).

CI / Docs / Chores

  • CI: добавлены httpx и respx в requirements-test.txt (mock httpx-запросов в тестах lyrics_client/zvuk_client; исправляет красный CI, появившийся с 0.35.0). Отражено в CLAUDE.md.
  • i18n: убран URL из service.play_music.url.description в translations/{en,ru}.json — hassfest translations validation ругался на URL в описании (commit dde54c7).
  • CI: actions/setup-python@v5 → v7 (dependabot).
  • Docs: sboom.play_musicsboom_ha.play_music в README (правильный DOMAIN); добавлен сервис в таблицу «Сервисы»; httpx>=0.27 внесён в раздел «Зависимости»; уточнён диапазон скорости 0.5×–2.0×.
  • CLAUDE.md: перегенерированы таблицы OP_* и MEDIA_CMD_* из const.py, добавлены MEDIA_CMD_START_MULTIROOM=8, OP_FIND_REMOTE=13, BT-семейство OP 19-22, OP_SET_PLAYBACK_SPEED=23. Обновлена архитектурная диаграмма: 10 платформ вместо 7, добавлен NetEase и Zvuk CDN как внешние зависимости.

[0.35.2]

Fixed

  • Ошибка customElements.define … "sboom-nowplaying" has already been used при открытии панели, когда на дашборде используется карточка sboom-card. Карточка импортирует общие компоненты без ?v, панель — с ?v=<версия>; разные URL заставляли браузер выполнять модуль дважды, а незащищённый define падал. Все shared-компоненты (sboom-nowplaying, sboom-controls, sboom-browse, sboom-track-row) теперь регистрируются идемпотентно (if (!customElements.get(...))), как sboom-toast/sboom-tile — двойная загрузка безопасна.

[0.35.1]

Changed

  • Рефактор фронтенда для переиспользования: data-feed логика панели (push-подписка sboom/subscribe + poll-fallback + devices/state/seek/toast-релей) вынесена в общий базовый класс SboomFeedBase (www/components/sboom-feed-base.js); дизайн-токены --sb-* — в www/components/sboom-tokens.css.js. Панель наследует базу без изменения поведения. Открывает путь для standalone Lovelace-карточки (sboom-card), переиспользующей те же компоненты и feed-слой.
  • Статика /sboom_panel теперь раздаётся всегда, независимо от опции panel_enabled — раздача www/ отвязана от регистрации боковой панели. Опция гейтит только пункт в боковом меню; общие компоненты остаются доступны для Lovelace-карточки sboom-card даже при выключенной панели.

[0.35.0]

Added

  • Встроенная премиум-панель плеера в боковом меню Home Assistant («SberBoom»): immersive-карточка (обложка во весь фон, фрост-стекло, ambient-glow из цвета обложки, скраббер, транспорт) + люксовые SVG-контролы. Оформление на нативных токенах темы HA (палитра/акцент/радиусы адаптируются к теме пользователя). Отображает версию интеграции; отзывчива до мобильного; на простое схлопывается.
  • Единый drill-down браузер список+поиск без табов: очередь → поиск (Исполнители/Альбомы/Треки/Плейлисты) → артист (аватар, топ-треки, дискография) → альбом (треклист) → трек. Навигационный стек, кнопка «назад», персистентный поиск.
  • Богатый поиск и метаданные Sber Звук — обложки, дискография, треклисты; работает с домашнего IP без VPN. Backend: standalone ZvukClient (get_tracks/get_artists/get_releases/get_artist/get_release, поиск, доминирующий цвет обложки) + WebSocket API панели.
  • Расширенные контролы: лайк/дизлайк (тюнинг персональной волны), скорость воспроизведения (0.5×–2.0×), найти пульт, shuffle/repeat, громкость.
  • Поддержка нескольких колонок — селектор устройства; команды/подписка/очередь/поиск адресуются по entry_id.
  • Опция включения/выключения панели (Settings → Integrations → SBoom → Configure → «Показывать панель плеера в боковом меню»).
  • Сервис sboom_ha.play_music — запуск по ссылке zvuk.com, поисковому запросу или id+kind.

Dependencies

  • Новая рантайм-зависимость httpx>=0.27 в manifest.json (добавлена ради ZvukClient и миграции lyrics_client). Устанавливается HA автоматически при обновлении.

Fixed

  • Мгновенные обновления панели (push-подписка на координатор вместо 5-сек поллинга); прогресс-бар якорится к серверному снимку и идёт плавно, корректен после перезагрузки.
  • Оптимистичные кнопки транспорта (мгновенная реакция); дедуп play-команд; «потерянные/неверные» тапы устранены (shouldUpdate-гейт, захват трека на pointerdown, приручённый рефетч очереди) — по итогам глубокого аудита.
  • Убран мусорный playlist_title из now-playing.

[0.29.1]

Fixed

  • Matter-сенсор пропадал (unavailable) при подключении реального Matter-устройства к колонке. Probe async_matter_probe искал слово «matter» в выводе matter list, но реальная таблица устройства (| NodeId | XID | Serial number | Model ID | … |) его не содержит → probe ложно возвращал Falsehas_matter_cli=False → сенсор не пересоздавался при рестарте (становился restored/unavailable). Теперь probe определяет доступность по отсутствию error-вывода CLI (не по слову «matter»).
  • matter list парсится в структуру (node_id / serial / model / RSSI): сенсор показывает список устройств в атрибутах, а count больше не считает строку-заголовок за устройство. Формат подтверждён на реальном устройстве (MTFFF40002).

[0.29.0]

Added

  • Опция «Караоке-закраска» (Options Flow) — посимвольная подсветка строки по мере пропевания. По умолчанию выключена: строка показывается статично. Когда off — доля пропетости (frac) не идёт ни в рендер, ни в кэш-ключ стрима, так что лишних кадров нет.

Known limitations

  • Закраска рассчитана на письмо слева-направо; для RTL (арабский/иврит) направление заливки не инвертируется (нужны bidi + shaping, напр. libraqm) — отдельная задача. Т.к. закраска off по умолчанию, на поведение «из коробки» не влияет.

[0.28.0]

Added

  • Заглушка-фон вместо чёрного экрана, когда обложка не нашлась (редкий BT-трек, радио без арта, сбой загрузки). Бандл из 4 свободных градиентов (StockSnap/Flickr, CC0 1.0 — атрибуция не требуется, см. backgrounds/CREDITS.txt). Выбор стабильно-случайный по треку (title|artist → индекс) — фон не мигает от кадра к кадру, но у разных треков разный. Применяется и к караоке-кадру, и к idle. Общий вес ассетов ~64 КБ.

[0.27.0]

Added

  • Обложка для Bluetooth и радио по исполнителю+треку. У некаталожного контента нет Zvuk-release_id, поэтому обложки не было (карточка и караоке-фон пустые). Теперь ищется по title+artist в публичных Search API без ключей: iTunes (апскейл до 600×600) → Deezer (резерв). Волатильно (один in-memory слот на трек, без диск-кэша). Подхватывается и в media_image_url (карточка media_player), и в blur-фон караоке-камеры. Радио тоже получает обложку текущей песни (обложке синк не нужен).
  • Кэш обложки в караоке-камере переведён с track_id на URL — раньше некаталожные треки (без track_id) делили один ключ.

[0.26.1]

Fixed

  • Радио исключено из караоке. Колонка отдаёт для радио позицию ЭФИРА (напр. 1224 c при песне 36–250 c), а не песни — синхронизировать лирику нечем. Лирика для радио больше не запрашивается, караоке-камера показывает idle (обложка/заголовок), а не замороженную последнюю строку. Bluetooth (позиция песенная) — работает и синхронизируется корректно (подтверждено вживую).
  • Диагностический сенсор _lyrics больше не показывает no_track для Bluetooth: теперь состояние лирики отражается и для некаталожного контента (у которого нет Zvuk track_id).

[0.26.0]

Added

  • Караоке/лирика для Bluetooth и радио — тянется напрямую из провайдеров (Lrclib → NetEase), без персистентного диск-кэша. У BT/радио нет глобального каталожного track_id (он есть только у контента Zvuk), поэтому:
    • ключ — синтетический title|artists; кэш каталога (по track_id) не затрагивается и не засоряется;
    • один in-memory слот на текущий трек — API не дёргается каждый poll, но и на диск ничего не пишется (BT — почти всегда одноразовая чужая музыка, радио — эфемерно);
    • контент без исполнителя (радио-реклама, джинглы) не запрашивается (гейт по artists).
    • BT синхронизируется по позиции/длительности от колонки; у радио позиция эфирная, поэтому синк приблизительный (сам текст показывается).

[0.25.0]

Fixed

  • Радио и Bluetooth больше не показываются как IDLE с пустой карточкой. У радио и BT-аудио нет trackId, поэтому get_metadata (op 10) для них пуст → трек был None → media_player висел в IDLE, хотя звук идёт. Теперь now-playing для них добирается из GET_STATE:
    • Радио: станция → media_channel (штатное поле HA), текущая песня → media_title, исполнитель → media_artist. На паузе остаётся станция, state = PAUSED (не IDLE).
    • Bluetooth (app bluetooth_media_control, а не music): title/исполнитель/альбом из player.info; media_source="BLUETOOTH".
    • Прогресс-бар скрывается у радио (duration=0None); обычная музыка/волна/подкаст (у них есть trackId) не затронуты.

Added

  • Сенсор sensor.<name>_playback_mode — тип воспроизведения: music / wave / podcast / radio / bluetooth. Единого поля-режима у колонки нет (radio по mode, wave/podcast по playlistType, bluetooth по источнику) — сенсор их сводит и работает даже когда media_player пуст.

[0.24.0]

Added

  • Слой межустройственной связки в GET_STATE. Раньше парсили только multiroom.mode/stereoPair.active; теперь читаем весь слой объединения устройств Sber:
    • Новый диагностический сенсор sensor.<name>_device_links (выкл. по умолчанию) — число связанных устройств (SberCast + группы селектора + саундбар), разбивка по источникам в атрибутах. На одиночной колонке 0; наполняется при объединении с SberBox/ТВ/другой колонкой (companion-протокол общий для платформы StarOS).
    • Сенсор multiroom_mode обогащён атрибутами стереопары: канал (L/R) и устройство-партнёр (multiroom.stereoPair.channelFromConfig/pairDeviceFromConfig).
    • Парсинг sbercast, deviceSelector (cast/ds/room/qc-группы), deviceGroups.soundBar — пустые источники отфильтровываются, ""-каналы нормализуются в None.

[0.23.2]

Changed

  • Честная документация op 18 (keep-alive). Эксперимент показал: транспортный WS ping/pong держит сессию сам (≥150 c idle без op 18 — соединение живо), а keep-alive-семантика op 18 нашим фаззингом не подтверждена. Комментарии в коде уточнены: op 18 оставлен как дешёвая application-level страховка, но его вклад в поддержание связи не изолирован. Поведение не менялось.

[0.23.1]

Changed

  • Документация синхронизирована под аппаратные возможности: карточка HACS info.md (датчики платы, инвентарь Zigbee/Matter) и дерево файлов README (iio_client.py, cli4242.py).

[0.23.0]

Added

Аппаратные возможности колонки (только на моделях, которые их реально имеют — напр. SberBoom R2; проверяется при старте, иначе сущности не создаются):

  • Сенсор освещённости (lux) и температуры SoC (°C) — с датчиков платы через libiio (:30431). Освещённость полезна для автоматизаций «стемнело → включи свет».
  • Инвентарь Zigbee (zigbee_inventory) — устройства умного дома, привязанные к колонке: количество + модели/производитель/RSSI/питание в атрибутах (через debug-CLI :4242). Только чтение инвентаря; состояние/управление CLI не даёт (идёт через облако Sber).
  • Инвентарь Matter (matter_inventory) — количество Matter-устройств + сырой вывод в атрибуте (формат строки структурируется при появлении реального устройства).

Все клиенты graceful: закрытый порт / отсутствие датчика = «недоступно», без ошибок.

[0.22.0]

Added

Ранее игнорировавшиеся данные GET_STATE (нулевой протокольный риск, схемы из живого захвата):

  • Сенсор «Канал прошивки» (device_segments, напр. OpenBeta) — понять, бета у вас или стабильная. Diagnostic.
  • Сенсор «Приложение на экране» (current_app) — приложение на переднем плане, в отличие от «Активное приложение» (играющее).
  • Атрибуты существующих сенсоров: весь z-order стек приложений (app_stack) у «Активное приложение»; смещение часового пояса у «Часовой пояс»; multi_profile/child_voice_explicit у «Возрастной режим»; from_show у бинарника «Утреннее шоу».

[0.21.0]

Added

  • Плашка источника на караоке-кадре — вверху приглушённым шрифтом показывается контекст «Персональная волна · Sber Звук» (название станции/плейлиста + провайдер) из тех же metadata-полей, что оживили Now Playing. И в караоке-режиме, и в idle. helpers.provider_label/source_label вынесены из media_player (дедупликация app_name).

[0.20.0]

Added

  • Богатый Now Playing — контекст трека в атрибутах media_player: playlist (название станции/плейлиста, напр. «Персональная волна»), playlist_type (endless/album/user), media_source (MUSIC/RADIO/…), provider, playlist_liked, child_mode, buffering. Всё уже приходило в metadata колонки — раньше просто не выставлялось (а playlistTitle и вовсе парсился, но терялся). Поля подтверждены живым захватом при переключении треков.

[0.19.2]

Changed

  • Календарь перерисовывается только при реальном изменении расписания. Раньше CalendarEntity наследовала обновление от координатора и пересчитывала RRULE всех будильников на каждый poll и каждый push трека/громкости. Теперь сверяется отпечаток расписания (id + ics/intervalSec/reminderTime, без тикающего timeLeftSec) — тик таймера и смена громкости больше не дёргают пересчёт календаря, а появление/удаление/редактирование события по-прежнему обновляет его сразу.

[0.19.1]

Changed

  • Документация синхронизирована под 0.19: карточка HACS info.md и mermaid-диаграмма README дополнены Calendar-сущностью.

[0.19.0]

Added

  • Calendar-сущность «Расписание» — будильники, таймеры и напоминания колонки одним календарём (calendar.<name>_schedule), синхронизируется с приложением «Салют». Будильники раскрываются из iCalendar RRULE (каждый день / будни / выбранные дни недели — корректный повтор), таймеры показываются событием до момента окончания, напоминания — по времени с заголовком. Категория события — в описании. Всё в UTC. Read-only (LAN-протокол не даёт op для создания событий).
  • Сенсоры «Следующий будильник» и «Следующий таймер» (sensor.*_next_alarm / _next_timer, device_class timestamp) — время ближайшего срабатывания для карточек и автоматизаций.

Форматы подтверждены живым захватом GET_STATE с колонки; парсинг покрыт тестами на реальной фикстуре (tests/fixtures/alarm_state.json).

[0.18.1]

Changed

  • Актуализирована документация под накопленные 0.14–0.18: README (диаграмма архитектуры с binary_sensor/device_tracker/lyrics_manager, цепочка Lrclib→NetEase, раздел про автоматизации) и карточка HACS info.md (караоке-подсветка, скорость, BT, device tracker, device-триггеры/действия, диагностические сенсоры, авто-восстановление IP).

[0.18.0]

Added

  • Device-триггеры и device-действия — команды и события колонки в UI-конструкторе автоматизаций без YAML. Триггеры: сменился трек / изменилось воспроизведение / громкость / связь (обёртки над событиями sboom_*, фильтр по конкретной колонке). Действия: play/pause/next/previous, обновить метаданные, найти пульт, Bluetooth-сопряжение. Чистый HA-слой, нового протокола не требуют.
  • Сенсор «Координаты» (диагностический, выключен по умолчанию) — сырые Wi-Fi-координаты колонки строкой lat, lon, а lat/lon/accuracy/source в атрибутах. В отличие от device_tracker (state = имя зоны, «Дома»/«Не дома»), показывает именно координаты — для карточек и шаблонов.

[0.17.0]

Added

  • device_tracker — местоположение колонки по Wi-Fi. Колонка сама сообщает свои координаты в GET_STATE (location{lat,lon,accuracy,source:"wifi"}), интеграция выставляет их GPS-трекером. Полезно для зон присутствия и карты. Выключен по умолчанию (координаты — чувствительные данные). Схема подтверждена реальными захватами.

[0.16.0]

Added

Новые сущности из данных, которые колонка уже присылала в GET_STATE, но интеграция их не читала (все схемы подтверждены реальными захватами, нулевой протокольный риск):

  • binary_sensor «Будильник звонит» (alarm.playing, device_class sound) — момент физического срабатывания будильника/таймера на колонке. Готовый триггер для сценариев «проснулся будильник → включи свет/кофеварку».
  • binary_sensor «У трека есть текст» (info.hasLyrics), «Автогромкость ассистента» (assistant.auto_volume), «Проактивное уведомление ассистента» (proactivityNotification).
  • Диагностические сенсоры (выключены по умолчанию): IP-адрес колонки (network.ip), часовой пояс (time.timezone_id), возрастной режим профиля (user_settings.age_mode), рассинхрон часов колонки с Home Assistant (timesync.unixtime − now) — помогает диагностировать сдвиг караоке/позиции.

[0.15.1]

Changed

  • Караоке рисуется напрямую текстом, без полнокадровых масок: белая строка + «пропетый» префикс акцентным цветом поверх (позиции глифов совпадают пиксель-в-пиксель). Символ загорается целиком — без разрезанных букв и артефактов маскирования по краям глифов; кадр подешевел с ~4 полнокадровых буферов до нуля (~9 мс на кадр — 20-кратный запас над 5 FPS).

[0.15.0]

Changed

  • Караоке-заливка стала посимвольной (по фидбеку из прода). Раньше маска резалась одним вертикальным срезом по всему боксу — многострочные тексты закрашивались на всех экранных строках одновременно. Теперь прогресс строки распределяется по символам в порядке чтения: экранные строки закрашиваются последовательно, срез внутри строки считается по метрикам шрифта, а слово автоматически получает время пропорционально своей длине (длинные слова «поются» дольше).

[0.14.2]

Fixed

  • Позиция трека сбрасывалась к началу каждые 15 секунд (регрессия 0.13.0, найдена пользователем в проде). Poll-ответ get_metadata несёт позицию на момент последнего события (для недавно начатого трека — 0), а не текущую; свежий штамп времени получения на этом stale-снапшоте перезапускал экстраполяцию на каждом poll-цикле. Теперь база экстраполяции переносится, если payload несёт тот же снапшот позиции (track_id + tsMs + position не менялись); новое событие (seek/пауза/смена трека) по-прежнему даёт свежий штамп.

[0.14.1]

Fixed

  • Служебные кредит-строки NetEase вычищаются из текста. LRC от NetEase начинается со строк вида [00:00.00] 作曲 : <композитор> — без фильтра кредит висел в караоке до первой настоящей строки (найдено живым тестом на «Всё идёт по плану» Егора Летова). Фильтруется сам synced-текст, чтобы кредиты не возвращались из персист-кэша после рестарта.

[0.14.0]

Added

  • Резервный источник текстов — NetEase Cloud Music. Если Lrclib не нашёл текст (или нашёл только plain без таймстампов), интеграция пробует NetEase: поиск трека с матчингом по названию/артисту/длительности (±7 с — чтобы не подцепить текст remix/live-версии), затем LRC-текст. Приоритет всегда у synced-текста. Отключается опцией lyrics_netease_fallback. Источник текста виден в атрибуте source lyrics-сенсоров.

[0.13.0]

Большой релиз по итогам глубокого код-ревью (85 подтверждённых находок): надёжность соединения, honest-соответствие HA Integration Quality Scale, плавное караоке, CI и масштабное покрытие тестами.

Fixed — надёжность и состояние

  • Half-open соединения теперь детектируются: включён WS ping/pong + принудительный reconnect после N подряд неудачных poll-циклов. Раньше обесточенная колонка выглядела «доступной» десятки минут.
  • Reconnect-backoff сбрасывается только после стабильной сессии — flapping-соединение больше не даёт вечный tight-loop с вечно available entities.
  • connected=True только после первого успешного запроса — колонка, принимающая WS, но не отвечающая, больше не считается подключённой.
  • Битый/частичный state-push не обнуляет громкость и device-сенсоры: parse_state возвращает None при провале разбора, недостающие поля домердживаются из прежнего состояния.
  • Позиция трека и караоке не зависят от часов колонки — экстраполяция от момента получения данных (monotonic) с учётом скорости воспроизведения; clock skew больше не сдвигает лирику.
  • volume_up/volume_down аккумулируются — optimistic-обновления состояния после команд (volume/mute/play/pause/shuffle).
  • Push-события больше не откладывают volume-poll и не отменяют подтверждение команд; устранена двойная перерисовка всех entities на каждый poll.
  • Healing-миграция unique_id не захватывает entry чужой колонки при переиспользовании IP (проверка device_id).
  • Порядок unload исправлен (сначала платформы, потом координатор); cleanup при ошибке setup; фоновые lyrics-задачи привязаны к entry; кэш лирики удаляется вместе с entry.
  • Pair-токен больше не пишется в лог открытым текстом; client_host редактируется в diagnostics.
  • TLV-декодер: wire-type 5 (fixed32) больше не обрывает молча разбор сообщения.

Added

  • Плавное караоке: заливка текущей строки акцентным цветом по мере пропевания (~5 FPS), прогресс-бар и таймер обновляются каждую секунду, повторяющиеся строки припева больше не замораживают кадр; кэш blur-фона и шрифтов; snapshot камеры рендерит lyrics-кадр и уважает запрошенный размер.
  • Настройка сдвига лирики (−10…+10 с) в Options; тик сенсора текущей строки планируется на границу следующей строки.
  • LRC: поддержка multi-timestamp строк и enhanced-LRC word-тегов.
  • Repairs fix-flow мигрирует legacy unique_id при смене host; сервисы получили schema-валидацию и ServiceValidationError вместо молчаливого игнора.
  • CI: pytest (Python 3.11–3.13) и ruff на каждый push/PR, dependabot, минимальные permissions.
  • Локализация сервисов через translations, шаг reauth в переводах, NumberSelector с единицами в Options, device_class для media_player (speaker) и lyrics-сенсора (enum).

Changed

  • Соответствие IQS: сервисы регистрируются в async_setup (action-setup), координатор в entry.runtime_data, логи доступности только на переходах (log-when-unavailable), quality_scale.yaml синхронизирован с реальностью.
  • Рефакторинг SOLID/DRY: lyrics-менеджер выделен из координатора, 14 boilerplate-классов сенсоров → декларативные спеки, дедупликация pair-handshake, единый repeat-маппинг, именованные константы протокола.
  • Треки без synced-текста дают state=None у сенсора строки вместо unavailable.

[0.12.0]

Устойчивость к смене IP-адреса колонки.

Added

  • Fixable Repairs-issue — когда колонка недоступна > 5 минут, из issue в Settings → System → Repairs теперь можно сразу ввести новый IP: соединение проверяется, entry обновляется и перезагружается. Pair-токен и device_id сохраняются, повторное сопряжение не требуется.

Fixed

  • Дубликаты при ручном добавлении. Entry, добавленный вручную по IP, получал unique_id, привязанный к адресу, и не матчился с zeroconf-discovery. После смены IP Home Assistant предлагал «новое устройство», а подтверждение создавало дубликат с повторным pairing. Теперь при первом же discovery такой entry автоматически мигрирует на unique_id по device_id — дальше смена IP лечится штатной soft-migration (host/port обновляются, интеграция перезагружается).
  • Ручной ввод IP уже настроенной колонки больше не создаёт второй entry — flow прерывается с already_configured по совпадению host.
  • Устаревший unique_id после reconfigure. Для entry с host-based unique_id смена адреса через Reconfigure обновляет и unique_id — старый IP больше не «занят» и не блокирует добавление другой колонки на нём.

[0.11.1]

Fixed

  • Сенсор «Активное приложение» флапал каждый poll-цикл, перебирая music/news/bluetooth_media_control/… Причина: значение бралось из background_apps[0], а это самотасующийся z-order стек — первый элемент меняется сам по себе. Теперь active_app — приложение с реально активным плеером (state.player.playing); если ничего не играет — состояние unknown.

[0.11.0]

Управление Bluetooth: сопряжение, спаренные устройства, поиск пульта.

Added

  • Кнопка «Bluetooth-сопряжение» — переводит колонку в режим сопряжения по Bluetooth. Длительность окна видимости задаёт прошивка колонки.
  • Кнопка «Найти пульт» — команда поиска пульта ДУ.
  • Сенсор «Спаренные Bluetooth-устройства» — количество + список (MAC, имя, статус подключения) в атрибутах.
  • Сервис sboom_ha.bluetooth_device — подключить / отключить / удалить спаренное BT-устройство по MAC.
  • Методы клиента: find_remote(), bt_make_discoverable(), get_paired_bt_devices(), get_scanned_bt_devices(), bt_device_command().
  • TLV-кодек: decode_repeated() — декод с поддержкой повторяющихся тегов (для списков устройств).

[0.10.0]

Персист lyrics-кеша + парсер-слой очереди воспроизведения.

Added

  • Персист lyrics-кеша. lyrics_to_dict() / lyrics_from_dict() + HA Store (JSON в .storage/). Кеш текстов песен загружается при старте интеграции и сохраняется с debounce — раньше жил только в памяти и терялся при каждом рестарте HA.
  • Парсер-слой очереди воспроизведения (op=17): QueueTrack, parse_queue(), SberSpeakerClient.get_queue() возвращает list[QueueTrack]. Низкоуровневая утилита без UI-сущности — очередь колонки отдаёт только trackId без названий, а резолв через Zvuk API требует авторизации (отдаёт 401 без токена).

Fixed

  • Lyrics-сенсор _tick вызывал async_write_ha_state() из executor-потока (RuntimeError в логе на HA 2026.5 / Python 3.14). Добавлен декоратор @callback — таймер-колбэк теперь исполняется в event-loop.

[0.9.0]

Сенсоры подсистем устройства из GET_STATE.

Added

  • Платформа binary_sensor — новая, 6 сущностей.
  • 13 read-only сущностей из уже приходящего GET_STATE (раньше из него брались только volume/muted):
    • sensor: яркость дисплея (%), будильники, таймеры, активное приложение, персона ассистента; multiroom-режим и тип подключения — diagnostic.
    • binary_sensor: дисплей включён, колонка активна (device_class: running), стереопара; устройство подписки, домашняя безопасность, утреннее шоу — diagnostic.
  • DeviceState — модель подсистем устройства; SpeakerState.device — поле с ней.
  • parse_device_state() — парсер подсистем GET_STATE.

Changed

  • parse_state() теперь извлекает сбалансированный JSON-объект и парсит подсистемы устройства. При битом JSON — fallback на прежний regex по volume, device=None.

[0.8.0]

Управление скоростью воспроизведения.

Added

  • Select-сущность «Скорость воспроизведения» (select.*_playback_speed). Пресеты 0.5×–2.0× с шагом 0.25. Скрыта на dashboard «Auto» по умолчанию (entity_registry_visible_default = False) — как и остальные side-feature сущности.
  • SberSpeakerClient.set_playback_speed() — команда op=23 (OP_SET_PLAYBACK_SPEED). Скорость кодируется как float (TLV wire-type 5, 4 байта LE IEEE-754); varint и nested-JSON ломают playbackSpeedRate колонки в 0.0 (подтверждено в research/exp_22). Значение жёстко ограничивается диапазоном 0.5–2.0.
  • TrackInfo.playback_speedplaybackSpeedRate из метаданных трека (push- и state-формат).
  • TLV-кодек (_tlv.field) получил поддержку kind=5 (fixed32 float).

[0.7.4]

Правки по итогам code-аудита всей интеграции.

Fixed

  • ConfigEntryNotReady при недоступной колонке на старте. Раньше первый connect шёл в фоне — интеграция «поднималась» в заведомо мёртвом состоянии, даже если колонка офлайн. Теперь первый connect выполняется синхронно в async_setup_entry; при неудаче HA откладывает и повторяет setup. Дальнейшие реконнекты по-прежнему ведёт фоновый supervisor.
  • Команды сущностей оборачивают транспортные ошибки в HomeAssistantError. При мёртвом WS медиа-команды (media_player, button, switch, number, select) раньше всплывали в UI сырым RuntimeError/ConnectionError с traceback'ом. Теперь — понятное переводимое сообщение.
  • MJPEG-стрим камеры устойчив к ошибкам рендера. Исключение PIL/отрисовки внутри stream-loop больше не роняет весь поток с HTTP 500 — кадр логируется и поток продолжается.
  • pair_with_button отклоняет вызов при активном listen-loop (раньше они молча конкурировали бы за recv()).
  • Reconfigure-flow проверяет соединение с новым host/port перед сохранением — опечатка в IP больше не уводит entry в вечно-битое состояние, показывается инлайн-ошибка.

Changed

  • DataUpdateCoordinator теперь получает config_entry явно (устраняет deprecation, ломавшийся бы в HA 2026.8).
  • PARALLEL_UPDATES объявлен на всех платформах.
  • Чистка мёртвого кода в api.py: убраны неиспользуемые KEEPALIVE_INTERVAL_SEC, поле _keepalive_task; импорты подняты в шапку модуля.
  • quality_scale.yaml приведён в соответствие реальности (action-exceptions, parallel-updates → done).

Tests

  • Тесты герметичны. Если в окружении установлен настоящий пакет homeassistant (например, venv соседнего проекта), он перехватывал импорты вместо stub'ов — тесты падали с невнятным Frame helper not set up. Теперь install_stubs() распознаёт ситуацию и падает сразу с понятной инструкцией про чистый venv.
  • Добавлен test_audit_fixes.py (143 теста суммарно): обёртка команд, поведение poll при обрыве, отмена pending-запросов в listen-loop, устойчивость _handle_event.

[0.7.3]

Fixed

  • Шумные ERROR-traceback'и при штатном обрыве связи. Когда колонка сбрасывала WS-соединение (перезагрузка, Wi-Fi-мигание, idle-disconnect), интеграция за один обрыв писала ~56 ERROR-traceback'ов: listen loop crashed + по два get_state failed / get_metadata failed на каждый poll-цикл в окне до reconnect. Функционально всё восстанавливалось, но лог захламлялся.
    • api._listen_loop теперь отличает штатный ConnectionClosed/OSError (→ один INFO без traceback) от настоящего сбоя (→ ERROR).
    • При выходе из listen-loop выставляется disconnected-событие — супервизор реагирует на разрыв мгновенно, не дожидаясь следующего keepalive-цикла (было до ~25s задержки).
    • Ожидающие запросы при обрыве немедленно отменяются вместо 5-секундного таймаута.
    • coordinator пропускает poll, пока соединение в обрыве, и логирует in-flight сбой компактным WARNING вместо полного traceback.

[0.7.2]

Changed

  • Default volume_poll_interval поднят с 5s до 15s. Эмпирически подтверждено в research-сессии (см. research/PROTOCOL.md): метаданные трека приходят push-events через subscribe-stream (активируется первым get_metadata-запросом). Polling нужен только для volume/mute, которые в push-stream НЕ попадают. 15s — разумный компромисс. Юзер может настроить через Options Flow если хочет иначе.
  • В supervisor добавлен комментарий объясняющий subscribe-механизм.

Migration

  • Существующие установки с явно установленным volume_poll_interval в options — продолжат работать с тем значением что юзер выбрал. Только default-значение изменилось.

[0.7.1]

Fixed

  • Hassfest validation: services.yaml исправлен — target.device.integration ушёл, вместо него fields.device_id.selector.device.integration. Тот же UX, корректный формат.

[0.7.0]

Большое обновление жизненного цикла интеграции — закрыли почти все Bronze/Silver правила HA Quality Scale.

Added

  • DiagnosticsSettings → Devices → SBoom → ⋮ → Download diagnostics отдаёт JSON со state coordinator, track/state, options и entry.data (с redaction токена/host/serial).
  • Options FlowSettings → Integrations → SBoom → Configure для тонкой настройки: volume_poll_interval, keepalive_interval, availability_threshold, lyrics_enabled. Авто-reload при изменении.
  • Reconfigure Flow — изменить IP/порт уже привязанной колонки без удаления интеграции (через ⋮ меню в Devices). PIN-токен сохраняется.
  • Reauth Flow — переавторизация без удаления entry. Запускается через service sboom_ha.reauth или будет триггериться автоматически когда выяснится сигнал колонки на invalid token.
  • Repairs platform — issue speaker_unreachable создаётся при недоступности колонки > 5 минут, удаляется при reconnect. Видно в Settings → System → Repairs.
  • System Health — метрики configured_speakers, connected_speakers, lrclib_reachable в Settings → System → System Information.
  • Custom services: sboom_ha.refresh_metadata (force-poll без ожидания цикла), sboom_ha.reauth (запустить переавторизацию). Поддержка таргетинга по device_id.
  • zeroconf reload_on_update=True — при смене IP колонки через mDNS-rediscover интеграция перезагружается автоматически, client начинает ходить на новый адрес.
  • async_migrate_entry + MINOR_VERSION=1 — заглушка миграций config entry для будущих изменений формата.
  • quality_scale: silver в manifest + quality_scale.yaml с пометками done/todo/exempt для движения к Gold.

Changed

  • Хардкоды → опции: volume_poll_interval (5s), keepalive_interval (25s), availability_threshold (3) перенесены из const.py в options. Дефолты сохранены.
  • Тестов: 105 → 135 (+30). Добавлены test_diagnostics, test_options, test_repairs, test_services, test_system_health.
  • Stubs HA: расширены до полной поддержки issue_registry, system_health, services, device_registry.async_get, ServiceCall, RepairsFlow, ConfirmRepairFlow.

[0.6.6]

Fixed

  • Brand-ассеты перенесены в правильное место: HA 2026.3+ использует Brands Proxy API — иконки кладутся в custom_components/{domain}/brand/, а home-assistant/brands репо больше не принимает PR для custom integrations. Папка brand-assets/ (не туда положенная в 0.6.5) удалена, файлы переехали в custom_components/sboom_ha/brand/{icon,icon@2x}.png.
  • Иконка теперь автоматически отображается в HA UI через /api/brands/integration/sboom_ha/icon.png — никаких сторонних PR не требуется.

[0.6.5]

Added

  • Brand-ассеты для HA UI: icon.png (256×256) и icon@2x.png (512×512), заимствованные из custom_integrations/sberdevices репо home-assistant/brands.
  • Изначально файлы были в brand-assets/, в 0.6.6 переехали в правильное место custom_components/sboom_ha/brand/.

[0.6.4]

Added

  • Индикация недоступности колонки: все entity (media_player, camera, sensor, button, switch, number, select) теперь показываются как Unavailable если WS-сессия с колонкой мертва. Реализовано через SboomCoordinator.connected + SboomEntity.available.
  • Event bus: sboom_connection_changed (поля connected, entry_id, device_id, host) — для автоматизаций «колонка отвалилась → push в Telegram» и подобных.

Changed

  • Debounce доступности (3 неудачные попытки): чтобы транзиентные мерцания сети не дёргали entity в Unavailable и обратно, флаг connected переключается в False только после 3 подряд неудачных reconnect'ов (DISCONNECT_THRESHOLD). Успешный connect мгновенно возвращает entity в available-состояние.
  • При connected-flip принудительно вызывается async_update_listeners() — UI перерисовывается мгновенно, без ожидания следующего poll-цикла.

[0.6.3]

Changed

  • CI: обновлены GitHub Actions до Node.js 24-совместимых версий — actions/checkout@v4 → @v6, actions/setup-python@v5 → @v6. Убрано предупреждение о deprecated Node.js 20 (forced removal 16 сентября 2026).

[0.6.2]

Fixed

  • Hassfest validation: ключи в manifest.json пересортированы (domain, name, далее алфавит) — требование Home Assistant для прохождения CI.

Changed

  • CI: убран hacs/action job — для приватного репо он бессмысленен (action ходит через публичный API без аутентификации). Hassfest и unit-тесты остались. Если репо когда-нибудь перейдёт в public и появится цель публиковаться через HACS-store — вернуть job обратно (см. git history) и добавить brand assets.

[0.6.1]

Added

  • Расширенный DeviceInfo в карточке устройства HA: firmware (sw_version) из mDNS-broadcast'а колонки и серийный номер (serial_number = device_id). Теперь видно прошивку и серийный сразу в Settings → Devices.
  • Soft-migration для существующих entries: при каждом zeroconf-rediscover (колонка регулярно сама анонсирует mDNS) firmware и model автоматически обновляются — старые установки получат firmware без переустановки.

Fixed

  • Firmware из mDNS извлекался при discovery, но не сохранялся в config entry — баг исправлен.

[0.6.0]

Added

  • HA event bus — интеграция выпускает события sboom_track_changed, sboom_playback_changed, sboom_volume_changed при изменениях на колонке. Триггерится через platform: event в автоматизациях. См. README раздел «События для автоматизаций».

Changed

  • Рефакторинг api.py — выделены модули _tlv.py (бинарный кодек), _parsers.py (JSON-парсеры payload'ов), _models.py (dataclasses TrackInfo/SpeakerState). api.py уменьшился с 692 до 399 строк. Публичный API сохранён через re-export в SberSpeakerClient.parse_track/parse_state.
  • Тестовая инфраструктура — добавлены tests/_ha_stubs.py (легковесные HA-stubs) и tests/_fakes.py (builders coordinator/track/state). Покрытие выросло с 48 до 94 unit-тестов: добавлены тесты media_player (entity properties), _tlv (изолированный кодек), coordinator_events (event bus).

Internal

  • pyproject.toml с конфигом для pytest и ruff (без [project] — версия остаётся в manifest.json).

[0.5.3]

Changed

  • Дополнительные сущности скрыты на dashboard по умолчанию (entity_registry_visible_default = False):
    • 7 buttons (next/prev/play_pause/like/dislike/remove_like/remove_dislike)
    • 1 number (volume)
    • 2 switches (shuffle, mute)
    • 1 select (repeat mode)
  • Эти entities остаются enabled (работают в автоматизациях, services, шаблонах) — просто не загромождают dashboard "Auto" / overview-карточки
  • Главные видимые entities: media_player + sensor.lyrics_current_line
  • Чтобы вернуть на dashboard: Settings → Devices → SBoom → нужная entity → toggle "Visible"

[0.5.2]

Added — first test suite

  • 48 unit-тестов: tests/test_helpers.py, test_lyrics_client.py, test_api_parsers.py, test_image_render.py
  • CI workflow для запуска тестов на каждом push (GitHub Actions)
  • Синтетические фикстуры payload-форматов (push + state-обёртка)

[0.5.1]

Refactor — подготовка к HACS publication

  • Вынесены общие утилиты в helpers.py (track_position, cover_url) — устранено дублирование между sensor.py, camera.py, media_player.py
  • SboomMediaPlayer теперь наследует от общего SboomEntity (DRY device_info)
  • device_info использует DeviceInfo dataclass вместо raw dict
  • Все вызовы asyncio.get_event_loop() заменены на asyncio.get_running_loop()
  • aiohttp.ClientTimeout вместо int в camera._fetch_cover_raw (совместимость с aiohttp 4.x)
  • Публичные методы media_remove_like(), media_remove_dislike(), parse_track(), parse_state() вместо вызова приватных
  • Использование MEDIA_CMD_* констант в api.py вместо magic numbers
  • Публичное свойство coordinator.http_session вместо обращения к _http
  • Исправлен двойной client.close() в config_flow.py pair-flow

[0.5.0]

Added

  • Tighter polling: update_interval 5s (было 30s) — громкость подтягивается быстрее
  • Прогресс-бар на camera — тонкая полоса + время MM:SS / MM:SS в стиле Яндекс.Музыки
  • Lyrics retry с fallback по artist+title если с album+duration не найдено

Fixed

  • Lrclib network errors теперь не кэшируются как "не найдено" — следующий push даёт retry

[0.4.1]

Added

  • Camera lyrics-overlay в стиле Яндекс.Музыки: blur-обложка + два уровня lyrics + footer с title/artist
  • Idle-режим с большой обложкой по центру + подписи

[0.4.0]

Added

  • Camera entity camera.<name>_lyrics_na_tv для karaoke-стрима
  • MJPEG-стрим с двумя строками lyrics (текущая + следующая) с переключением по таймингу LRC
  • Snapshot-mode для предпросмотра в HA UI
  • Возможность отправки на ТВ через media_player.play_media
  • Зависимость Pillow для PIL-рендера, шрифт DejaVuSans включён в пакет

[0.3.0]

Added

  • Lyrics через Lrclib.net (open API, без auth, синхронизированные LRC)
  • sensor.<name>_lyrics_current_line — текущая строка (тикает 1 Гц, state пишется только при смене)
  • sensor.<name>_lyrics (диагностический, включается вручную) — полный текст в attributes
  • Кэш lyrics в coordinator с фоновым fetch при смене трека

[0.2.1]

Fixed

  • Добавлено media_content_type=MUSIC — теперь HA more-info показывает артиста в secondary-строке вместо app_name

[0.2.0]

Added

  • Auto-discovery через Zeroconf (_staros._tcp.local.)
  • MediaPlayer с volume/mute/play/pause/next/prev/seek/shuffle/repeat
  • Cover image из public Zvuk CDN (https://cdn-image.zvuk.com/pic)
  • Sub-platforms: button (like/dislike/transport), number (volume), switch (shuffle/mute), select (repeat)

[0.1.0]

Added

  • Первичная реализация через PIN_AUTH WebSocket-сессию
  • Pair-flow с нажатием + на колонке
  • Базовый media_player entity