mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
121 lines
14 KiB
Markdown
121 lines
14 KiB
Markdown
# Матрица настроек света устройства
|
|
|
|
Актуально для локальной реализации issues [#84](https://github.com/Matysh/houseplan-card/issues/84)
|
|
и [#88](https://github.com/Matysh/houseplan-card/issues/88). Каноническая модель
|
|
геометрии и распространения света остаётся в [`LIGHT.md`](LIGHT.md).
|
|
|
|
## Обозначения
|
|
|
|
- **A** — режим «Авто» нашёл у устройства собственный пространственный источник.
|
|
- **S** — у устройства есть собственная управляемая `light.*`/`switch.*`, способная
|
|
дать состояние и принять service call.
|
|
- **Источник** — устройство участвует в Glow, заливке «Свет», карточке и статистике комнаты.
|
|
- **Live** — доступен режим цвета «Из источника».
|
|
- **Ручн.** — доступны ручной цвет и ручная яркость.
|
|
- **R** — доступен локальный радиус Glow.
|
|
- `Авто → фикс.` — сохранённый режим не переписывается, но в UI и runtime пассивный
|
|
источник получает безопасный fallback: общий цвет, яркость 100%.
|
|
|
|
Комбинация `A=да, S=нет` приведена для полноты контракта и мутационных тестов, но
|
|
недостижима штатным resolver: автоматически найденный источник всегда имеет реальную
|
|
`light.*`. Остальные 27 строк достижимы.
|
|
|
|
## Полная матрица role × A × S × режим Glow
|
|
|
|
| # | Роль | A | S | Сохранённый режим | Источник | Пассивный | Live | Ручн. | R | Эффективный режим |
|
|
|---:|---|:---:|:---:|---|:---:|:---:|:---:|:---:|:---:|---|
|
|
| 1 | Авто | нет | нет | Из источника | нет | нет | нет | нет | нет | Из источника, disabled |
|
|
| 2 | Авто | нет | нет | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled |
|
|
| 3 | Авто | нет | нет | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled |
|
|
| 4 | Авто | нет | да | Из источника | нет | нет | нет | нет | нет | Из источника, disabled |
|
|
| 5 | Авто | нет | да | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled |
|
|
| 6 | Авто | нет | да | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled |
|
|
| 7 | Авто | да | нет | Из источника | да | да | нет | да | да | Авто → фикс. (теоретическая) |
|
|
| 8 | Авто | да | нет | Задать цвет | да | да | нет | да | да | Задать цвет (теоретическая) |
|
|
| 9 | Авто | да | нет | Цвет + яркость | да | да | нет | да | да | Цвет + яркость (теоретическая) |
|
|
| 10 | Авто | да | да | Из источника | да | нет | да | да | да | Из источника |
|
|
| 11 | Авто | да | да | Задать цвет | да | нет | да | да | да | Задать цвет |
|
|
| 12 | Авто | да | да | Цвет + яркость | да | нет | да | да | да | Цвет + яркость |
|
|
| 13 | Всегда | нет | нет | Из источника | да | да | нет | да | да | Авто → фикс. |
|
|
| 14 | Всегда | нет | нет | Задать цвет | да | да | нет | да | да | Задать цвет |
|
|
| 15 | Всегда | нет | нет | Цвет + яркость | да | да | нет | да | да | Цвет + яркость |
|
|
| 16 | Всегда | нет | да | Из источника | да | нет | да | да | да | Из источника |
|
|
| 17 | Всегда | нет | да | Задать цвет | да | нет | да | да | да | Задать цвет |
|
|
| 18 | Всегда | нет | да | Цвет + яркость | да | нет | да | да | да | Цвет + яркость |
|
|
| 19 | Всегда | да | нет | Из источника | да | да | нет | да | да | Авто → фикс. (теоретическая) |
|
|
| 20 | Всегда | да | нет | Задать цвет | да | да | нет | да | да | Задать цвет (теоретическая) |
|
|
| 21 | Всегда | да | нет | Цвет + яркость | да | да | нет | да | да | Цвет + яркость (теоретическая) |
|
|
| 22 | Всегда | да | да | Из источника | да | нет | да | да | да | Из источника |
|
|
| 23 | Всегда | да | да | Задать цвет | да | нет | да | да | да | Задать цвет |
|
|
| 24 | Всегда | да | да | Цвет + яркость | да | нет | да | да | да | Цвет + яркость |
|
|
| 25 | Никогда | нет | нет | Из источника | нет | нет | нет | нет | нет | Из источника, disabled |
|
|
| 26 | Никогда | нет | нет | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled |
|
|
| 27 | Никогда | нет | нет | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled |
|
|
| 28 | Никогда | нет | да | Из источника | нет | нет | нет | нет | нет | Из источника, disabled |
|
|
| 29 | Никогда | нет | да | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled |
|
|
| 30 | Никогда | нет | да | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled |
|
|
| 31 | Никогда | да | нет | Из источника | нет | нет | нет | нет | нет | Из источника, disabled (теоретическая) |
|
|
| 32 | Никогда | да | нет | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled (теоретическая) |
|
|
| 33 | Никогда | да | нет | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled (теоретическая) |
|
|
| 34 | Никогда | да | да | Из источника | нет | нет | нет | нет | нет | Из источника, disabled |
|
|
| 35 | Никогда | да | да | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled |
|
|
| 36 | Никогда | да | да | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled |
|
|
|
|
Ручной цвет при stateful-источнике оставляет живую яркость. Режим «Цвет + яркость»
|
|
фиксирует оба значения. Для пассивного источника живой яркости нет, поэтому «Задать
|
|
цвет» означает яркость 100%, а «Цвет + яркость» использует сохранённое значение.
|
|
|
|
## Ведущая сущность (#88)
|
|
|
|
| Роль | Управляемых собственных сущностей | Сохранённый `light_entity` | UI и runtime |
|
|
|---|---:|---|---|
|
|
| Авто / Никогда | любое число | любое значение | Селектор скрыт; поле сохраняется без изменения, но на текущую роль не влияет |
|
|
| Всегда | 0 | отсутствует | Пассивный источник, селектор скрыт |
|
|
| Всегда | 1 | отсутствует | Единственная сущность выбирается автоматически, селектор скрыт |
|
|
| Всегда | 2+ | отсутствует | Селектор показан; fallback `binding → primary → первая управляемая` |
|
|
| Всегда | 1+ | валидное значение | Выбранная сущность даёт state и service target |
|
|
| Всегда | любое число | сущность исчезла | Предупреждение; временный fallback, сохранённая ссылка не стирается |
|
|
|
|
Выбор не зависит от текущего `on/off/unavailable`: capability берётся из binding и
|
|
реестра HA, а live state обрабатывается отдельно.
|
|
|
|
## Связи «Управляет другими источниками света» (#84)
|
|
|
|
| Цель в `controls` | Состояние цели | Service call контроллера | Glow и статистика |
|
|
|---|---|---|---|
|
|
| `light.*` / `switch.*` | Фактическое состояние entity; группа — `any(on)` | Только реальные entity IDs | Реальный отдельный marker владеет позицией; иначе цель участвует без отдельного пятна у контроллера |
|
|
| `marker:<id>` stateful | Состояние ведущей сущности цели | Ведущая сущность цели, с дедупликацией | Позиция, комната, цвет и радиус принадлежат marker-цели |
|
|
| `marker:<id>` passive, один controller | Его реальные targets, иначе собственная ведущая entity | Сам `marker:*` никогда не отправляется в HA | Пассивная лампа следует controller и светит в своей позиции |
|
|
| Passive, несколько controllers | OR всех активных driver entities | По каждому действию — только его реальные targets | Один источник и один голос комнаты, без дублей |
|
|
| Passive без сохранённых links | Всегда `on` | Нет собственного вызова | Постоянный Glow и `1 из 1` |
|
|
| Links есть, но все drivers скрыты/disabled/удалены | `off` / dormant | Нет вызова по битой цели | Ссылка сохраняется, пятна нет |
|
|
| Прямая entity + `marker:` на тот же stateful source | Одно effective состояние | Один service target | Один источник и один голос |
|
|
| Target переведён из «Всегда» в «Авто без источника»/«Никогда» | Dormant | Нет marker-service | Link сохраняется и оживает при возврате «Всегда» |
|
|
| Target скрыт или disabled в HA | Dormant | Нет marker-service | Не рисуется и не влияет на комнату |
|
|
| Target удалён с плана | Ссылка удаляется атомарно | Нет | Устройство снова можно добавить заново |
|
|
| Broken legacy ref | Игнорируется с диагностикой | Нет | Open → Save не уничтожает ссылку |
|
|
| Self-link / новый цикл | Запрещено | — | UI не предлагает, backend отклоняет запись/import |
|
|
|
|
Связи разрешены между комнатами и пространствами одного плана. При экспорте одного
|
|
пространства внутренние `marker:`-ссылки ремапятся вместе с marker ID, а внешние
|
|
отбрасываются с предупреждением в preview.
|
|
|
|
## Независимые переключатели отображения
|
|
|
|
| Настройка | Влияние на световую модель |
|
|
|---|---|
|
|
| «Значок + состояние» | Подложка устройства показывает его resolved working state; Glow определяется матрицей выше |
|
|
| «Значок + состояние и активность» | Дополнительно показывает короткую или постоянную пульсацию; источник света не меняется |
|
|
| «Значение + состояние» | Меняет только содержимое маркера; источник света не меняется |
|
|
| «Всегда статичный значок» | Блокирует динамику самого значка/подложки, но не отменяет отдельно настроенные Glow, заливку «Свет» и статистику комнаты |
|
|
| Glow выключен у пространства | Пятна не рисуются, но источник, состояние комнаты и заливка «Свет» продолжают вычисляться |
|
|
| Маркер скрыт / HA-disabled | Маркер и его собственный источник не участвуют в плане независимо от остальных настроек |
|
|
|
|
## Проверяемый контракт
|
|
|
|
- Все 36 строк первой таблицы проверяются pure unit-тестом.
|
|
- Выбор ведущей сущности, stale fallback, passive OR, dormant links, alias-dedupe,
|
|
lifecycle и запрет передачи `marker:*` в HA покрыты отдельными unit-тестами.
|
|
- Контекстный селектор, passive-гейтинг и plan-source picker покрыты browser smoke.
|
|
- Backend отдельно проверяет новые ссылки, циклы и перенос между пространствами.
|