mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
fix v1.7.2: mobile 'configuration error' on cold start — register card as Lovelace resource (awaited before render) instead of only add_extra_js_url (raced); idempotent version update + YAML-mode fallback; _loadFromServer retries (up to 8 hass ticks) so card doesn't stick on onboarding if WS not ready yet
This commit is contained in:
@@ -63,13 +63,60 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass.http.register_static_path(PLANS_URL, str(plans_path), cache_headers=True)
|
hass.http.register_static_path(PLANS_URL, str(plans_path), cache_headers=True)
|
||||||
hass.http.register_static_path(FILES_URL, str(files_path), cache_headers=True)
|
hass.http.register_static_path(FILES_URL, str(files_path), cache_headers=True)
|
||||||
|
|
||||||
if card_path.exists():
|
if not card_path.exists():
|
||||||
add_extra_js_url(hass, f"{FRONTEND_URL}?v={VERSION}")
|
|
||||||
else:
|
|
||||||
_LOGGER.warning("houseplan-card.js не найден рядом с интеграцией: %s", card_path)
|
_LOGGER.warning("houseplan-card.js не найден рядом с интеграцией: %s", card_path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Подключаем карточку. Предпочтительно — как Lovelace-ресурс (его фронтенд ДОЖИДАЕТСЯ
|
||||||
|
# перед рендером дашбордов, поэтому карточка доступна даже на холодном старте мобильного
|
||||||
|
# приложения). Если реестр ресурсов недоступен (YAML-режим Lovelace, старые версии) —
|
||||||
|
# откатываемся на extra_module_url.
|
||||||
|
module_url = f"{FRONTEND_URL}?v={VERSION}"
|
||||||
|
if not await _register_lovelace_resource(hass, module_url):
|
||||||
|
add_extra_js_url(hass, module_url)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _register_lovelace_resource(hass: HomeAssistant, module_url: str) -> bool:
|
||||||
|
"""Зарегистрировать (или обновить) карточку в реестре Lovelace-ресурсов.
|
||||||
|
|
||||||
|
Возвращает True при успехе. Пишем идемпотентно: если ресурс с нашим путём уже есть —
|
||||||
|
обновляем URL при смене версии; отсутствует — создаём. Any-except → False (фолбэк на JS).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
lovelace = hass.data.get("lovelace")
|
||||||
|
resources = getattr(lovelace, "resources", None)
|
||||||
|
if resources is None and isinstance(lovelace, dict):
|
||||||
|
resources = lovelace.get("resources")
|
||||||
|
if resources is None:
|
||||||
|
return False
|
||||||
|
# реестр ресурсов должен быть загружен
|
||||||
|
if hasattr(resources, "loaded") and not resources.loaded:
|
||||||
|
await resources.async_load()
|
||||||
|
resources.loaded = True
|
||||||
|
elif hasattr(resources, "async_get_info"):
|
||||||
|
await resources.async_get_info()
|
||||||
|
# только storage-режим позволяет создавать элементы
|
||||||
|
if not hasattr(resources, "async_create_item"):
|
||||||
|
return False
|
||||||
|
base = FRONTEND_URL
|
||||||
|
existing = [
|
||||||
|
item for item in resources.async_items()
|
||||||
|
if str(item.get("url", "")).split("?", 1)[0] == base
|
||||||
|
]
|
||||||
|
if existing:
|
||||||
|
item = existing[0]
|
||||||
|
if item.get("url") != module_url and hasattr(resources, "async_update_item"):
|
||||||
|
await resources.async_update_item(item["id"], {"url": module_url})
|
||||||
|
return True
|
||||||
|
await resources.async_create_item({"res_type": "module", "url": module_url})
|
||||||
|
_LOGGER.debug("House Plan card зарегистрирована как Lovelace-ресурс: %s", module_url)
|
||||||
|
return True
|
||||||
|
except Exception as err: # noqa: BLE001 — любой сбой → фолбэк
|
||||||
|
_LOGGER.debug("Не удалось зарегистрировать Lovelace-ресурс (%s), фолбэк на extra_module_url", err)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
hass.data[DOMAIN]["entry"] = entry
|
hass.data[DOMAIN]["entry"] = entry
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -10,7 +10,7 @@ PLANS_DIR = "houseplan/plans" # относительно каталога ко
|
|||||||
FILES_URL = "/houseplan_files/files"
|
FILES_URL = "/houseplan_files/files"
|
||||||
FILES_DIR = "houseplan/files"
|
FILES_DIR = "houseplan/files"
|
||||||
CONF_ADMIN_ONLY = "admin_only"
|
CONF_ADMIN_ONLY = "admin_only"
|
||||||
VERSION = "1.7.0"
|
VERSION = "1.7.2"
|
||||||
|
|
||||||
DEFAULT_CONFIG: dict = {
|
DEFAULT_CONFIG: dict = {
|
||||||
"spaces": [],
|
"spaces": [],
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -15,5 +15,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"issue_tracker": "https://github.com/justbusiness/houseplan-card/issues",
|
"issue_tracker": "https://github.com/justbusiness/houseplan-card/issues",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"version": "1.7.0"
|
"version": "1.7.2"
|
||||||
}
|
}
|
||||||
Vendored
+11
-11
File diff suppressed because one or more lines are too long
@@ -28,9 +28,11 @@ houseplan-card/
|
|||||||
|
|
||||||
## Ключевые решения
|
## Ключевые решения
|
||||||
|
|
||||||
1. **Один репозиторий — интеграция + карточка.** Интеграция сама раздаёт JS
|
1. **Один репозиторий — интеграция + карточка.** Интеграция раздаёт JS
|
||||||
(`hass.http.async_register_static_paths` + `frontend.add_extra_js_url`), пользователю не
|
(`async_register_static_paths`) и регистрирует его как **Lovelace-ресурс** (module) —
|
||||||
нужно прописывать ресурс Lovelace. Паттерн как у browser_mod/xiaomi_vacuum_map.
|
фронтенд дожидается ресурсов перед рендером, поэтому карточка работает и на холодном старте
|
||||||
|
мобильного приложения (в отличие от `add_extra_js_url`, который остаётся фолбэком для
|
||||||
|
YAML-режима). Пользователю не нужно вручную добавлять ресурс.
|
||||||
2. **Раскладка иконок — на сервере.** `helpers.storage.Store(1, "houseplan.layout")` →
|
2. **Раскладка иконок — на сервере.** `helpers.storage.Store(1, "houseplan.layout")` →
|
||||||
`.storage/houseplan.layout`. Карточка читает/пишет через `hass.callWS`
|
`.storage/houseplan.layout`. Карточка читает/пишет через `hass.callWS`
|
||||||
(`houseplan/layout/get|set|update`). Fallback — localStorage (если интеграции нет).
|
(`houseplan/layout/get|set|update`). Fallback — localStorage (если интеграции нет).
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.7.2 — 2026-07-04 (фикс мобильного «ошибка конфигурации»)
|
||||||
|
- ПРИЧИНА: карточка подключалась только через `add_extra_js_url` (extra_module_url), загрузку
|
||||||
|
которого фронтенд НЕ дожидается — на холодном старте мобильного приложения дашборд рисовался
|
||||||
|
раньше регистрации элемента → «ошибка конфигурации». (Все рабочие HACS-карточки — Lovelace-ресурсы.)
|
||||||
|
- ФИКС 1: интеграция регистрирует карточку как **Lovelace-ресурс** (module) — их фронтенд ждёт
|
||||||
|
перед рендером. Идемпотентно (обновляет URL при смене версии, без дублей); фолбэк на
|
||||||
|
extra_module_url для YAML-режима Lovelace. `_register_lovelace_resource` в __init__.py.
|
||||||
|
- ФИКС 2: `_loadFromServer` ретраит (до 8 попыток по обновлениям hass) — если hass пришёл раньше
|
||||||
|
готовности WS, карточка не застревает на онбординге, а дожидается конфига.
|
||||||
|
- Проверено: ресурс houseplan-card.js зарегистрирован, extra_module_url пуст (единый чистый
|
||||||
|
модуль), карточка рендерит план на свежей вкладке без ошибки.
|
||||||
|
|
||||||
## v1.7.0 — 2026-07-04 (аудит + рефакторинг + тесты)
|
## v1.7.0 — 2026-07-04 (аудит + рефакторинг + тесты)
|
||||||
- Удалён вшитый в бандл дом-образец (дача, ~245 КБ base64 планов + ROOMS/FLOOR_*): бандл
|
- Удалён вшитый в бандл дом-образец (дача, ~245 КБ base64 планов + ROOMS/FLOOR_*): бандл
|
||||||
293 КБ → 83 КБ. Свежая установка показывает онбординг «Добавить пространство», а не чужой дом.
|
293 КБ → 83 КБ. Свежая установка показывает онбординг «Добавить пространство», а не чужой дом.
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "houseplan-card",
|
"name": "houseplan-card",
|
||||||
"version": "1.7.0",
|
"version": "1.7.2",
|
||||||
"description": "Interactive house plan Lovelace card for Home Assistant (dacha Kirillovskoe)",
|
"description": "Interactive house plan Lovelace card for Home Assistant (dacha Kirillovskoe)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+19
-11
@@ -13,7 +13,7 @@ import {
|
|||||||
} from './logic';
|
} from './logic';
|
||||||
import './editor';
|
import './editor';
|
||||||
|
|
||||||
const CARD_VERSION = '1.7.0';
|
const CARD_VERSION = '1.7.2';
|
||||||
const LS_KEY = 'houseplan_card_layout_v1';
|
const LS_KEY = 'houseplan_card_layout_v1';
|
||||||
const NORM_W = 1000; // ширина рендер-пространства для нормированных конфигов
|
const NORM_W = 1000; // ширина рендер-пространства для нормированных конфигов
|
||||||
|
|
||||||
@@ -121,7 +121,9 @@ class HouseplanCard extends LitElement {
|
|||||||
private _edit = false;
|
private _edit = false;
|
||||||
private _layout: Record<string, { x: number; y: number; s?: string }> = {};
|
private _layout: Record<string, { x: number; y: number; s?: string }> = {};
|
||||||
private _serverStorage = false;
|
private _serverStorage = false;
|
||||||
private _loaded = false;
|
private _loadOk = false;
|
||||||
|
private _loading = false;
|
||||||
|
private _loadTries = 0;
|
||||||
private _serverCfg: ServerConfig | null = null;
|
private _serverCfg: ServerConfig | null = null;
|
||||||
private _cfgRev = 0;
|
private _cfgRev = 0;
|
||||||
private _unsubCfg: (() => void) | null = null;
|
private _unsubCfg: (() => void) | null = null;
|
||||||
@@ -321,8 +323,7 @@ class HouseplanCard extends LitElement {
|
|||||||
|
|
||||||
protected willUpdate(changed: PropertyValues): void {
|
protected willUpdate(changed: PropertyValues): void {
|
||||||
if (changed.has('hass') && this.hass) {
|
if (changed.has('hass') && this.hass) {
|
||||||
if (!this._loaded) {
|
if (!this._loadOk && !this._loading && this._loadTries < 8) {
|
||||||
this._loaded = true;
|
|
||||||
this._loadFromServer();
|
this._loadFromServer();
|
||||||
}
|
}
|
||||||
this._maybeRebuildDevices();
|
this._maybeRebuildDevices();
|
||||||
@@ -332,11 +333,14 @@ class HouseplanCard extends LitElement {
|
|||||||
// ================= сервер: конфиг + раскладка =================
|
// ================= сервер: конфиг + раскладка =================
|
||||||
|
|
||||||
private async _loadFromServer(): Promise<void> {
|
private async _loadFromServer(): Promise<void> {
|
||||||
|
this._loading = true;
|
||||||
|
this._loadTries++;
|
||||||
try {
|
try {
|
||||||
const [cfgResp, layResp] = await Promise.all([
|
const [cfgResp, layResp] = await Promise.all([
|
||||||
this.hass.callWS({ type: 'houseplan/config/get' }),
|
this.hass.callWS({ type: 'houseplan/config/get' }),
|
||||||
this.hass.callWS({ type: 'houseplan/layout/get' }),
|
this.hass.callWS({ type: 'houseplan/layout/get' }),
|
||||||
]);
|
]);
|
||||||
|
this._loadOk = true;
|
||||||
this._serverStorage = true;
|
this._serverStorage = true;
|
||||||
const cfg = cfgResp?.config;
|
const cfg = cfgResp?.config;
|
||||||
this._serverCfg = cfg && Array.isArray(cfg.spaces) ? cfg : null;
|
this._serverCfg = cfg && Array.isArray(cfg.spaces) ? cfg : null;
|
||||||
@@ -352,14 +356,18 @@ class HouseplanCard extends LitElement {
|
|||||||
this._space = this._model[0]?.id || this._space;
|
this._space = this._model[0]?.id || this._space;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._serverStorage = false;
|
// не последняя попытка — молча ждём следующего обновления hass (прогрев WS)
|
||||||
this._serverCfg = null;
|
if (this._loadTries >= 8) {
|
||||||
try {
|
this._serverStorage = false;
|
||||||
this._layout = JSON.parse(localStorage.getItem(LS_KEY) || '{}') || {};
|
this._serverCfg = null;
|
||||||
} catch {
|
try {
|
||||||
this._layout = {};
|
this._layout = JSON.parse(localStorage.getItem(LS_KEY) || '{}') || {};
|
||||||
|
} catch {
|
||||||
|
this._layout = {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this._showToast('Интеграция houseplan не найдена — позиции сохраняются локально');
|
} finally {
|
||||||
|
this._loading = false;
|
||||||
}
|
}
|
||||||
this._regSignature = '';
|
this._regSignature = '';
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|||||||
Reference in New Issue
Block a user