yellow means working: one principle for the glowing icon
Validate / hacs (push) Failing after 6s
Validate / hassfest (push) Failing after 7s
Validate / frontend (push) Failing after 1m33s
Validate / smoke (push) Skipped
Validate / backend (push) Failing after 6m17s

Research on the owner's install (verified live): the radiator heads that
glowed yellow were the ones with SCALE PROTECTION on, and the ones actually
heating stayed dark. Cause: the primary-entity search ran domains outside
tiers, and switch outranks climate — so a vendor's config switch (anti
scaling, child lock) became the device's primary, driving the color, the
icon morphing and tap-toggle alike.

The principle now: yellow = the device is doing its main job RIGHT NOW.

- primaryEntity: tiers outside, domains inside — a service entity never
  beats the visible main function; a hidden lamp still beats a visible
  config switch (grouped lights), and a plug's switch stays primary.
- climate joins the state table: yellow by hvac_action (heating/cooling/
  drying/fan) — 'which radiators are heating', not 'enabled for winter';
  the coarser state is only a fallback when the integration reports no
  action.
- one truth for light: litLightEntity() is asked by BOTH the glow pool and
  the icon color, in every fill mode — the pool and the icon can no longer
  disagree. The 'is a light source' flag keeps counting controls first.
- README (en+ru): the color table, in words.

Tests: TRV + plug primary units, litLightEntity unit, smoke_yellow_principle
(heating yellow / idle dark / off dark / fallback / lit-light wins / forced
source). Inventory: 142 / 51 / 43 / 67.
This commit is contained in:
Matysh
2026-07-29 11:07:34 +03:00
parent 098a147f87
commit 996a7442ec
9 changed files with 198 additions and 46 deletions
+60
View File
@@ -0,0 +1,60 @@
// Единый принцип жёлтого (2026-07-29): жёлтый = устройство прямо сейчас
// выполняет основную функцию. Термоголовка желтеет от реального нагрева
// (hvac_action), а не от служебного свитча защиты от накипи; горящая лампа
// желтит значок в любом режиме заливки тем же условием, что зажигает пятно.
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch();
const out = await page.evaluate(() => {
const o = {};
const c = window.__card;
const cls = (d, states) => {
const saved = c.hass;
c.hass = { ...c.hass, states: { ...c.hass.states, ...states } };
const r = c._stateClass(d);
c.hass = saved;
return r;
};
const trv = { id: 't', primary: 'climate.trv', entities: ['climate.trv', 'switch.trv_anti_scaling'], marker: null };
// реально греет → жёлтая
o.heatingIsYellow = cls(trv, {
'climate.trv': { state: 'heat', attributes: { hvac_action: 'heating' } },
'switch.trv_anti_scaling': { state: 'on' },
}) === 'on';
// включена, но не греет (idle) → чёрная, даже со включённой защитой от накипи
o.idleIsDark = cls(trv, {
'climate.trv': { state: 'heat', attributes: { hvac_action: 'idle' } },
'switch.trv_anti_scaling': { state: 'on' },
}) === '';
// выключена → чёрная
o.offIsDark = cls(trv, {
'climate.trv': { state: 'off', attributes: { hvac_action: 'off' } },
}) === '';
// без hvac_action фолбэк на state
o.stateFallback = cls(trv, { 'climate.trv': { state: 'heat', attributes: {} } }) === ''
? false : cls(trv, { 'climate.trv': { state: 'heat', attributes: {} } }) === 'on';
// горящая лампа устройства желтит значок, даже если primary — не она
const lamp = { id: 'l', primary: 'sensor.lamp_power', entities: ['sensor.lamp_power', 'light.lamp'], marker: null };
o.litLightWins = cls(lamp, {
'sensor.lamp_power': { state: '5' },
'light.lamp': { state: 'on' },
}) === 'on';
o.darkLampIdle = cls(lamp, {
'sensor.lamp_power': { state: '5' },
'light.lamp': { state: 'off' },
}) === '';
// «источник света»: умный выключатель с глупыми светильниками — жёлтый от
// того же условия, что и пятно glow
const wall = { id: 'w', primary: 'sensor.w', entities: ['sensor.w'],
marker: { is_light: true, controls: ['switch.fixtures'] } };
o.forcedSourceYellow = cls(wall, {
'sensor.w': { state: '1' }, 'switch.fixtures': { state: 'on' },
}) === 'on';
return o;
});
await finish(browser, checkAll(out));
File diff suppressed because one or more lines are too long