Skip to content

DALI configurator: live device controls on the device page - #1223

Draft
evgeny-boger wants to merge 16 commits into
masterfrom
feat/dali-device-controls
Draft

DALI configurator: live device controls on the device page#1223
evgeny-boger wants to merge 16 commits into
masterfrom
feat/dali-device-controls

Conversation

@evgeny-boger

@evgeny-boger evgeny-boger commented Aug 29, 2026

Copy link
Copy Markdown
Member

Встраивает живые контролы устройств прямо в DALI-конфигуратор — коммишенинг устройства и проверка результата происходят в одном месте — и готовит страницу ко второму дому: автономному WASM-редактору устройств.

Как устроено

  • Контролы. DeviceControls на вкладках устройства, группы, шины (broadcast) и датчика; питаются только обычными retained-топиками демона (/devices/<mqtt_id>/controls/…) и переиспользуют существующие стор Cell и виджеты контролов.
  • Закреплённая полоса под тулбаром (на телефонах — bottom sheet): живые показания с выделенным значением («Освещённость 1: 576»), основные слайдеры, выбор цвета, Off и Recall Max Level; пары уставка↔показание объединены — «Wanted Level (1.8 %)», Set RGB с живым образцом цвета; остальное — за кнопкой «Все контролы». Эвристики pickFeatured/mergeReadbacks — чистые функции, юнит-тесты лежат рядом с кодом.
  • Страж схемы событий. Устройству DALI-2, чьи инстансы используют схему адресации событий, не позволяющую назвать отправителя (заводскую по умолчанию), показывается предупреждение с кнопкой исправления в один клик — такие события видны в мониторе, но не могут обновлять контролы (SOFT-7398).
  • Монитор шины: вкладки-консоли по шинам; подряд идущие одинаковые строки ошибок сворачиваются в бейдж ×N (чистый хелпер, покрыт тестами); открытая панель, когда мониторинг нигде не включён, объясняет это и предлагает включить его по кнопке — для каждой шины.
  • Host capabilities (daliHostCapabilities): контролы, осмысленные только на контроллере («Сохранить в syslog»), закрыты флагами, которые встраивающий хост очищает до первого рендера; подмена вкладки шлюза для WASM делается модульной подстановкой на этапе сборки. Копирование топика по клику — тоже за политикой, для хостов без брокера.
  • Мелочи, которые видно. TabToolbar публикует измеренную высоту, чтобы липкая полоса вставала вплотную при любом переносе строк; неудачный GetGroup показывает Retry вместо пустой вкладки; GroupStore.controlsMqttId владеет формулой id виртуального устройства группы.
  • Строки ru/en полные, включая русские формы множественного числа; файлы локалей несут только добавленные ключи, без переформатирования.

Проверено

На реальном железе через автономный редактор (WB-DALI @17, светильники Skydance DT8, датчик Tridonic MSensor): полосы на вкладках устройства, группы и шины; объединение уставок с показаниями вживую; баннер схемы, починивший 18 неверно сконфигурированных инстансов реального датчика; сворачивание строк в мониторе; bottom sheet на телефоне.

Тесты

npm test — 2506 тестов, включая новые сьюты: эвристики полосы, сворачивание строк ошибок, страж схемы, соответствие mqtt-id, формула id группы, контракт единственного переключения CollapsiblePanel. tsc --noEmit чисто; npm run lint чисто для затронутых файлов. По ветке прошло ревью по 9 аспектам, включая правила проекта; все замечания исправлены в ветке.

Связанные: wirenboard/wb-wasm-device-editor#96 (встраивающий хост), SOFT-7398.

Скриншоты

Интерфейс на русском, симулированная шина через автономный редактор (сняты e2e-ригом).

Вкладка шины: широковещательная полоса — яркость, цветовая температура, Выкл, максимальная яркость
Широковещательные контролы шины

Страница светильника: полоса с объединённым показанием «(0 %)» и форма идентификации DT6
Страница устройства

Открытый монитор без единой включённой шины объясняет себя и предлагает включить мониторинг по кнопке
Пустой монитор

Живой монитор: DT8-запросы цвета и ShortPress от стороннего мастера с бейджем «сторонний ×3»
Монитор с трафиком

Телефон: полоса контролов складывается в bottom sheet

Bottom sheet на телефоне

Точки встраивания для хоста. Флаги daliHostCapabilities закрывают то, что имеет смысл только на контроллере: переключатель «Сохранить в syslog» и видимость редактора MQTT-id. За политикой же копирование топика по клику — для хостов без брокера. Хелпер sendCellValueUpdate — единственное написание команды записи контрола (/devices/<id>/controls/<ctl>/on, non-retained), общее для полосы контролов и DevicesStore. На контроллере всё это по умолчанию ведёт себя как раньше.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q4sGCd4GhAUr3ySuYvAGFk

Embeds the device's runtime controls — the same brightness, state, colour and
command controls wb-mqtt-dali publishes over MQTT for the Devices view — into
the DALI configurator's device page, so commissioning a device and trying the
result happen in one place.

Everything flows through the daemon's ordinary retained topics
(/devices/<mqtt_id>/controls/... values, /meta JSON, /meta/error, writes to
/on), rendered with the existing Cell store and cell widgets, so the panel
works identically against a controller's broker and the standalone WASM
editor's in-browser loopback broker — no environment-specific code.

Verified in the standalone editor against a real WB-DALI with a Skydance DT8
lamp: the panel lists the daemon's full control set (levels, DAPC, step and
scene commands, RGB/W), dragging Wanted Level lit the lamp and Actual Level
polled back through the daemon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
@codacy-production

codacy-production Bot commented Aug 29, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 duplication

Metric Results
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

evgeny-boger added a commit to wirenboard/wb-wasm-device-editor that referenced this pull request Aug 29, 2026
…page

Bumps the submodule to wirenboard/homeui#1223: each device page now carries
the daemon's runtime controls (levels, commands, colour), rendered from the
ordinary MQTT control topics — which the loopback broker serves here exactly
as a controller's broker does. Verified against the real WB-DALI: dragging
Wanted Level lit the lamp, Actual Level polled back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
evgeny-boger and others added 10 commits August 29, 2026 14:58
The controls card sat above the config form with nothing signalling that the
form continues below it — the page read as one strange form, and users looked
for a way to "switch" between controls and configuration. The same
collapsible-section idiom the bus page uses for Broadcast settings makes the
two-section structure visible and lets the controls fold to one header line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
The stacked controls card failed exactly where live controls matter most: a
DALI-2 sensor publishes ~75 cells, 72 of them per-button event indicators, so
the three readings someone actually watches while tuning parameters —
occupancy, movement, illuminance — scrolled away instantly, buried in noise,
above a 14,500 px form. Prototyped six layouts against real hardware; this is
the winner.

One concept at every width: a small featured set that never leaves the
screen, the full control set one gesture away.

- Featured cells are chosen by structure, not device-specific names: read-only
  readings that are not part of a per-instance family (detected by stripping a
  trailing index and counting siblings — "Button 2".."Button 18" is a
  17-member family, "Occupied 0" a singleton), plus the primary actuator range
  and the Off button. A sensor pins Occupied / Movement / Illuminance; a lamp
  pins Ok / Actual Level / Current RGB / Current W / Wanted Level / Off.
- Wide screens: the featured cells ride in a slim strip, sticky under the
  toolbar, visible through the whole form scroll. Everything else sits behind
  a collapsed "All controls" section.
- Phones: the same featured set becomes a bottom sheet — a 44 px peek bar with
  live values in thumb reach, opening to the full grid scrolling inside 60vh
  while the form keeps the entire screen.

Verified on hardware at 1360 px and 390 px: the sensor strip stays pinned at
6,000 px of form scroll; the lamp strip carries its slider and Off; the peek
bar live-updates and the sheet opens with all 19 lamp cells.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
…nitor

Two findings from the hardware UX review.

The content pane had no heading: which device was being edited was carried
only by the tree selection — invisible on mobile, where the tree is hidden
while a page is open. Bus, device and group pages now carry a title, with the
device's bus as context.

And a DALI-2 device initializing probes absent features three times per
instance, painting the whole monitor viewport red with retries of the same
fact. Consecutive identical error rows now collapse into one with a ×N badge
— on a real MSensor init, 63 rows become 21. Only error rows collapse:
ordinary traffic keeps its one-row-per-frame timeline, and foreign frames
their ring counters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
Clicking a cell's name copies its MQTT topic id and shows "copied to
clipboard" — useful on a controller, where rules and dashboards take topics.
The standalone WASM device editor drives the same cells from an in-browser
loopback with no broker to paste into, so the toast there has no possible
next step. A small policy switch lets such a host turn the copy (and its
tooltip) off once at startup; controllers keep the default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
…; strip-anchored "All controls"

The device, bus and group tabs share a TabToolbar: the page title and the
action buttons (Identify/Reload/Reset/Save, or Rescan) on one sticky row,
instead of the title floating on a line of its own above them.

Bus and group pages gain the live-controls panel the device pages have. The
daemon publishes the bus broadcast and each group as virtual devices
("<bus>_broadcast", "<bus>_group_NN"); pointing DeviceControls at those ids
gives every-member-at-once control right where the settings are — the first
thing one wants after a scan or after editing a group.

The featured-controls strip's "All controls" toggle moved into the strip
itself. As a header below the sticky strip it scrolled underneath it and
became unclickable exactly when reached for; in the strip it is always
on screen. GroupStore exposes its parent bus, like DeviceStore, so the
virtual-device id can be built.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
…or the bus cannot do

The featured strip now folds each readback into its setpoint — Wanted Level
carries "(actual %)", Set RGB a live colour swatch — so the pair spends one
slot, and the freed room features colour pickers, secondary sliders and
Recall Max Level next to Off. Readings get a colon and a bold value:
"Illuminance 1 576" no longer reads as 1576.

A device whose instances use an event addressing scheme that names no sender
gets a warning banner with a one-click fix: such events decode in the monitor
but can never update the controls (IEC 62386-103 Table 8 — only
device short + instance number carries both halves of the attribution).

New host-capabilities switchboard: the standalone WASM editor turns off the
syslog monitor toggle and the Lunatone WebSocket emulator — a browser page
has no syslog and cannot listen on a port. Alert cells stop offering
copy-the-topic when the host has no broker, same policy as cell names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
…strip alignment

A failed GetGroup (a busy bus times the RPC out) rendered the group tab as
nothing at all; it now says what happened and offers a retry.

The command-catalog button hid its label under 990px in favour of an icon it
does not have, leaving a label-less pill; the label stays.

The featured strip parked at a hard-coded offset under the toolbar — wrong
whenever the title or buttons wrapped, letting form rows show through above
it or clipping its first line. The toolbar now publishes its measured height
(--dali-toolbar-height, ResizeObserver) and the strip parks flush against it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DPrWsZjKprJZVq5y6oGQfu
Clicking the Bus Monitor button with every bus's monitor off produced a
bare drawer — no tabs, no text, indistinguishable from a rendering bug.
The console panel accepts an emptyState node for the no-tabs case, and the
DALI page supplies one: why the panel is empty, plus a button per bus that
enables monitoring right there (the same action as the toggle at the bottom
of that bus's tab, which the hint also points to).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4sGCd4GhAUr3ySuYvAGFk
…ests beside the code

The locale files return to their original 3-space formatting: the diff
against main shows the eight added keys instead of 4500 reformatted lines.
The peek line speaks the UI language (state-on/state-off), the repeat badge
gets Russian plural forms, and the orphaned device-controls key is gone.

The gateway tab keeps exactly one host-adaptation seam — the WASM editor's
build-time module substitution; the capability-flag fallback was unreachable
in both hosts and referenced a translation namespace homeui does not own.
GroupStore owns the group virtual-device id formula (controlsMqttId); the
monitor's error-row folding moves into a pure, tested helper; the cell
publish topic has one spelling shared with DevicesStore.

Project-rules pass: getters after the constructor, ItemType over bare
literals, props in types.ts, a typed InstanceConfig instead of casts, and
the inert useMemo wrappers around the strip heuristics are gone.

Tests move to where the code merges: the featured-strip heuristics are now
covered in this repo (they were guarded only from the embedding repo), plus
new tests for error-row folding, the event-scheme guard, mqtt-id mapping,
the group id formula and the collapsible panel's single-toggle contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4sGCd4GhAUr3ySuYvAGFk
evgeny-boger and others added 2 commits August 30, 2026 17:33
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4sGCd4GhAUr3ySuYvAGFk
The shared form-buttonGroup never wraps, so on a 390px screen the four
device actions stacked on top of each other. Scoped to the DALI toolbar
rather than changed globally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
evgeny-boger and others added 2 commits August 31, 2026 09:09
On a controller the id is the device's MQTT identity — dashboards, rules
and history refer to it, so editing it is meaningful. The WASM editor runs
a loopback broker nothing external ever sees; there the field is an
implementation detail with no possible next step. A new host capability,
externalBroker, lets that host drop the editor while the daemon keeps its
default id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…guration

GetGroup merges parameters over the group's members that have finished
initializing; while none has — during boot-time initialization, or while
the member lamps are unpowered — it legitimately answers an empty schema.
The store cached that moment as final, so a group tab opened too early
showed only the controls strip for the rest of the session.

An empty answer now marks the store as awaiting members: the tab says the
devices are still initializing and re-asks every few seconds until the
parameters arrive, with no reload needed. A loaded group is still never
refetched.

Note: the existing "skips if already loaded" test used an empty-schema
fixture, which now means "still initializing" by design — its fixture
became a non-empty schema so it keeps pinning its original claim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve conflicts:
- bus-tab-content: keep master's 115200-baud note under the bus monitor
  toggle and the branch's host-capability gating of the syslog toggle
- debian/changelog: put 2.250.0 on top of 2.249.1/2.249.2
@evgeny-boger

Copy link
Copy Markdown
Member Author

Разбит на независимые PR от master (каждый — своя единица, свой bump changelog, черновики):

Этот PR остаётся черновиком как интеграционная ветка: на неё указывает сабмодуль homeui в wirenboard/wb-wasm-device-editor#96. После мержа пяти PR выше сабмодуль переводится на master, а этот PR закрывается.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant