Releases: TohaRG2/sboom_ha
Release list
v0.37.1
[0.37.1] — 2026-08-15
Fixed — ошибка «expected str … Got None» в панели
WS-команды панели (sboom/queue, sboom/state, sboom/subscribe и др.) падали
с ошибкой валидации, если панель обращалась к ним до загрузки списка колонок
(entry_id ещё null). Схема теперь принимает null — команда адресуется
первой доступной колонке. Симптом — «Ошибка очереди» при открытии каталога.
[0.37.0] — 2026-08-15
Added — эквалайзер и настройки колонки в панели и карточке
Панель и Lovelace-карточка SberBoom получили блок «Звук колонки»: эквалайзер
(включение, пресеты Flat/Рок/Поп/Джаз/Книга/Бас, 5-полосная регулировка) и полезные
переключатели колонки (детский режим, звуки ассистента, подсветка). Управление идёт
через сущности интеграции sberhome на том же устройстве колонки — интеграции
«поженены» общим идентификатором, поэтому карточка находит эквалайзер автоматически.
Блок сам скрывается, если у колонки нет эквалайзера или sberhome не установлена.
Новый переиспользуемый компонент sboom-equalizer.js (общий для панели и карточки).
Команда sboom/devices теперь отдаёт serial колонки — мост к сущностям sberhome.
[0.36.0] — 2026-08-08
Волна 5 (финальная) по итогам code review (REVIEW_REPORT.md): архитектурный рефакторинг. Поведение не меняется, внутренние границы — да (MINOR).
Changed
- Единый командный слой
coordinator.async_execute(action, value)(аудит #18). Один источник политики «команда → вызов клиента → optimistic-патч → refresh»:media_player,switch/number/selectи ws-командаsboom/commandпанели стали тонкими адаптерами. Побочно чинит несогласованность:switch/numberтеперь делают optimistic-патч (переключатель mute и слайдер громкости больше не «отпрыгивают» в окне debounce),select/repeat — единообразно. - Единый источник deeplink и инфраструктурных хелперов (аудит #17, #19). Новый модуль
_ha_helpers.py:get_zvuk_client(hass)(singleton, ключconst.ZVUK_CLIENT_KEY) иiter_coordinators(hass)вместо трёх скопированных реализаций вservices/websocket_api/device_action. Маппинг zvuk-URL→deeplink — только вZvukClient(см. 0.35.3). - Публичный
client.send_raw_request(envelope, msg_id, timeout)(аудит #16)._deeplink.pyбольше не лезет в приватныеclient._ws/_pending/_lock— инкапсуляция корреляции request/response вынесена в клиент;_request_responseпереиспользует её. - Аппаратная подсистема выделена в
HwMonitor(аудит #20). iio-датчики + Zigbee/Matter CLI со своим probe/poll и снапшот-полями — внеSboomCoordinator(SRP). Доступ сенсоров —coordinator.hw.*; старыйcoordinator.iio_cap/… сохранён как property-делегаты. - DRY-вычистки: общий
_draw_frame_chromeдля караоке- и idle-кадров (аудит #45);coordinator.device_stateвместо трёх копий_dev()(аудит #46);LyricsManager._run_fetch+helpers.track_identity_keyдля обоих менеджеров (аудит #47).
Removed
- Мёртвый код (аудит #43):
api.AuthError,client.get_scanned_bt_devices(op=21 не использовался),image_render.draw_cover/draw_lyrics(заменены yandex-версиями),cli4242.matter_count,coordinator.matter_raw,SpeakerState.track.
Fixed
- Инкапсуляция (аудит #44): публичные
LyricsManager.supports_track()иcoordinator.stoppingвместо импорта приватного_synthetic_keyвsensor.pyи чтенияcoord._stoppingвdiagnostics.py;SboomSensorSpec.state_class/device_classтипизированы enum'амиSensorStateClass/SensorDeviceClassвместо строковых литералов.
Docs
- Зафиксирован трейдофф WSS без верификации сертификата (аудит #40) в разделе «Известные ограничения» README: self-signed сертификат колонки, модель угроз — доверенная LAN, возможный TOFU-пиннинг в будущем.
[0.35.6] — 2026-08-08
Волна 4 исправлений по итогам code review (REVIEW_REPORT.md): устойчивость, hardening парсеров, гигиена ошибок.
Fixed
- Hardening парсеров на недоверенных payload'ах (все репродукции из ревью превращены в регрессионные тесты):
parse_trackбольше не теряет трек со скобками{/}в названии/имени артиста (аудит #1): вместо string-слепого backward-скана — перебор кандидатов-{со string-aware балансировкой и проверкой охватаtrackId.parse_trackпринимает числовойtrackIdбез кавычек (аудит #25), как это уже делалtrack_from_state.parse_stateне срывается на байте 0x7b в бинарном TLV-префиксе — пробует следующую{(аудит #27); DeviceState (~20 сенсоров) больше не пропадает на такой цикл.parse_stateпереживает мусорныйvolume.percent(null/строка) — поле остаётся None, merge сохраняет прежнее (аудит #28; раньше TypeError превращал живую колонку в ConfigEntryNotReady).decode/decode_repeatedпроверяют границы для length-delimited и fixed32 — обрезанный TLV-кадр даёт частичный результат, а не битые значения (аудит #26).seek_toclamp'ит отрицательную позицию (аудит #12): varint кодировал −1 как 127 для колонки.
- Сервисы отдают внятные ошибки (аудит #37).
play_music/bluetooth_deviceоборачивают транспортные сбои колонки (RuntimeError/TimeoutError/ConnectionError/OSError) вHomeAssistantErrorвместо сырого traceback «Unknown error» в UI. ZvukClient.searchдержит контракт «ошибка → пусто» (аудит #22).get_tokenперенесён внутрь try: недоступность zvuk.com на этапе токена больше не пробиваетservices.play_musicсырым httpx-исключением.- Lrclib: битый 200-ответ не роняет background-task (аудит #23).
ValueError(невалидный JSON) ловится, не-dict тело трактуется как сетевая аномалия (retry позже). - Камера не долбит лежащий CDN (аудит #39). Неудачный URL обложки кэшируется на 60 с — idle-стрим отдаёт фон-заглушку сразу, вместо скачивания с 10-секундным таймаутом на каждый кадр.
- Diagnostics больше не дезинформирует (аудит #42). Поле
last_update_success(всегда True — сбои полла глотаются намеренно) убрано из выгрузки; честная доступность —connected.
[0.35.5] — 2026-08-08
Волна 3 исправлений по итогам code review (REVIEW_REPORT.md): конкурентность и жизненные циклы ресурсов.
Fixed
- Гонка last-write-wins poll vs push/optimistic закрыта (аудит #5). Введён generation-счётчик мутаций
coordinator.state/track: push-события и optimistic-патчи инкрементируют его, а poll снимает снапшот перед await и отбрасывает свой ответ, если данные изменились во время ожидания. Раньше стейл-ответ poll'а откатывал optimistic-громкость слайдера и свежий push-трек (и стрелял дубль-событиями). - Жизненный цикл боковой панели согласован между entries (аудит #14). Панель существует, пока её хочет хотя бы один загруженный entry (
_sync_panel): entry сpanel_enabled=Falseбольше не сносит панель другой колонки при своей перезагрузке; выгрузка последнего entry убирает пункт из меню (раньше он оставался ссылаться на мёртвый backend до рестарта HA); регистрация статики защищена от TOCTOU (маркер до await'ов). - Подписка панели переживает reload entry (аудит #32). Координатор получил
async_add_stop_listener;sboom/subscribeшлёт терминальное событие{terminated: true}при остановке координатора, фронтенд переподписывается на новый инстанс. Раньше панель «замирала» на мёртвом координаторе до перезагрузки страницы. ZvukClient.aclose()больше не мёртвый код (аудит #31). Разделяемый клиент закрывается поEVENT_HOMEASSISTANT_STOP; ключ singleton'а вынесен вconst.ZVUK_CLIENT_KEY(раньше единственность держалась на совпадении строк в двух модулях).- CLI-опрос :4242 стал в ~10 раз быстрее (аудит #15).
_drainвыходит по тишине (idle-timeout 0.3 c) вместо сжигания полного read-timeout 3 c после последнего чанка: каждая команда стоила ~3.6 c внутри цикла координатора (Zigbee+Matter — ~7 c задержки poll'а и setup). - Стейл-результаты volatile-fetch отбрасываются (аудит #33). LyricsManager и CoverManager запоминают ключ последнего запрошенного трека: при смене A→B поздний результат A больше не перезаписывает слот (мерцание карточки, лишний повторный fetch).
fallback_coverне блокирует event loop (аудит #34). Первое чтение CC0-фонов с диска ушло вasyncio.to_thread.
[0.35.4] — 2026-08-08
Волна 2 исправлений по итогам code review (REVIEW_REPORT.md): заметные пользователю баги.
Fixed
- Караоке-кадр не терял CJK-лирику и длинные слова (аудит #10).
_layout_textпереносил строки регэкспом, требующим пробел в конце фрагмента — текст без пробелов длиннееline_widthмолча терял начало (оставался только хвост). Заменено наtextwrap.wrap(break_long_words=True). - Снапшот камеры не падает на тексте-простыне (аудит #11). Рекурсия уменьшения шрифта в
_layout_textне имела нижней границы: длинный текст уводилfont_sizeв ≤0 →ValueErrorиз PIL. Введена граница 12. - MJPEG-стрим отдаёт кадры в BT-режиме без позиции (аудит #9). В
_stream_idleначальноеlast_sec=Noneсовпадало сcur_sec=None(у BT-трека позиции нет) — не рисовался ни один кадр. Теперь sentinel: первый кадр рисуется всегда. - Смена песни на BT/радио видна сразу, а не через volume-poll (аудит #7). Push GET_STATE теперь пересобирает now-playing (
_maybe_update_track_from_state): раньше title/artist обновлялись с задержкой до 15 с. Каталожный трек не затирается — его обновляет OP_GET_META_DATA. - Поиск Звука оживает после протухания токена (аудит #6).
_graphqlиsearchпри 401 делают один retry сget_token(force=True); раньше протухший анонимный токен молча убивал поиск/каталог/play_musicдо рестарта HA. - Device-триггеры работают для вручную добавленных колонок (аудит #4). Единый
helpers.sber_device_id(fallbackCONF_DEVICE_ID or host) используется и в identifiers устройства, и в событиях HA bus: раньше события неслиdevice_id=None, а триггер фильтровал по host — все 4 device-триггера молча не срабатывали. - Позиция длинного трека не откатывается на 601-й секунде (аудит #13). Дельта экстраполяции...
v0.35.2
[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.75×–2×), найти пульт, shuffle/repeat, громкость.
- Поддержка нескольких колонок — селектор устройства; команды/подписка/очередь/поиск адресуются по
entry_id. - Опция включения/выключения панели (Settings → Integrations → SBoom → Configure → «Показывать панель плеера в боковом меню»).
- Сервис
sboom.play_music— запуск по ссылке zvuk.com, поисковому запросу или id+kind.
Fixed
- Мгновенные обновления панели (push-подписка на координатор вместо 5-сек поллинга); прогресс-бар якорится к серверному снимку и идёт плавно, корректен после перезагрузки.
- Оптимистичные кнопки транспорта (мгновенная реакция); дедуп play-команд; «потерянные/неверные» тапы устранены (shouldUpdate-гейт, захват трека на pointerdown, приручённый рефетч очереди) — по итогам глубокого аудита.
- Убран мусорный
playlist_titleиз now-playing.
[0.29.1]
Fixed
- Matter-сенсор пропадал (
unavailable) при подключении реального Matter-устройства к колонке. Probeasync_matter_probeискал слово «matter» в выводеmatter list, но реальная таблица устройства (| NodeId | XID | Serial number | Model ID | … |) его не содержит → probe ложно возвращалFalse→has_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: теперь состояние лирики отражается и для некаталожного контента (у которого нет Zvuktrack_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=0→None); обычная музыка/волна/подкаст (у них есть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) — устройства умного дома, привязанные к колонке: количество + модели/производитель/R...
0.11.1
[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()+ HAStore(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_speed—playbackSpeedRateиз метаданных трека (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
[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
- Diagnostics —
Settings → Devices → SBoom → ⋮ → Download diagnosticsотдаёт JSON со state coordinator, track/state, options и entry.data (с redaction токена/host/serial). - Options Flow —
Settings → 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/actionjob — для приватного репо он бессмысленен (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(dataclassesTrackInfo/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используетDeviceInfodataclass вместо 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.pypair-flow
[0.5.0]
Added
- Tighter polling:
update_interval5s (было 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 ...