Grey out what Home Assistant calls off and colour the rest like Home Assistant, so an airco that is off no longer looks on (0.2.85)

The airco tile stayed orange while the airco was off. The firmware kept its own
short list of what turns grey and gave an off climate Home Assistant's orange,
but Home Assistant has no colour for a climate that is off: its cards fall back
to the inactive grey. Other tiles had the same fault: an off fan, a closed
blind, a docked robot, a player in standby, an idle script, a paused timer and
a camera that isn't streaming all kept their colour.

Tile::active() is now a port of the frontend's stateActive() for the domains a
tile can show, and every tile that is not active is grey. An airco that is off
reads Off, with the room's temperature when the device reports one, as Home
Assistant's tile does. tile_controls::accent() follows stateColorCss(): alarm
binary sensors red, a battery green, orange or red by its charge, a running
script or timer and a streaming camera amber, the weather per condition, the
sun amber and indigo, someone in a zone blue. Scenes, selects, numbers and
sensors keep their own colour per kind. A full-page tile lights up only for
tiles that can be off (Tile::lights_up), and a closed blind's slider keeps its
colour. The top bar's alarm classes are Home Assistant's eleven.

Checked with tools/check.sh (471 Python tests, 19/19 C++ with 58 state cases
and every colour rule) and compiled for both boards: CYD 1,677,488 B (91.4 %,
+576 B), Guition 2,054,640 B (25.3 %).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-18 23:31:10 +02:00
parent d76461d774
commit 9391d1b0ac
16 changed files with 369 additions and 69 deletions
+63
View File
@@ -59,6 +59,69 @@ inline uint32_t mode_color(const std::string &mode) {
if (mode == "dry") return ORANGE;
return GREY;
}
// Binary sensor classes Home Assistant draws red while they are on (its --state-binary_sensor-<class>-on-color): an
// alarm, a problem, a low battery, an unlocked lock. header_bar.ALARM_CLASSES gives the top bar the same list.
inline bool alarm_class(const std::string &device_class) {
for (const char *alarm : {"battery", "carbon_monoxide", "gas", "heat", "lock", "moisture", "problem", "safety", "smoke",
"sound", "tamper"})
if (device_class == alarm) return true;
return false;
}
// Home Assistant's colour for a weather condition (--state-weather-<condition>-color); a condition it has none for
// takes its --state-active-color.
inline uint32_t weather_color(const std::string &condition) {
using namespace theme::ha;
if (condition == "sunny") return AMBER;
if (condition == "clear-night") return DEEP_PURPLE;
if (condition == "partlycloudy") return BLUE_GREY;
if (condition == "cloudy") return LIGHT_GREY;
if (condition == "fog") return GREY;
if (condition == "rainy") return BLUE;
if (condition == "pouring") return INDIGO;
if (condition == "snowy") return ICE;
if (condition == "snowy-rainy") return LIGHT_BLUE;
if (condition == "hail") return CYAN;
if (condition == "lightning") return YELLOW;
if (condition == "lightning-rainy") return LIME;
if (condition == "windy" || condition == "windy-variant") return GREEN;
if (condition == "exceptional") return RED;
return AMBER;
}
// The colour of a tile while Home Assistant calls it active (Tile::active; anything inactive is grey): its
// stateColorCss() (frontend src/common/entity/state_color.ts) for the domains it colours by state, and a colour of
// our own per kind for those it draws in one neutral blue, such as scenes, selects, numbers and sensors (0.2.3).
// A state Home Assistant has no colour of its own for takes its --state-active-color, amber (firmware 0.2.71+).
// Colours set in a Lovelace card or theme are no entity attributes and never reach the screen.
inline uint32_t accent(const Tile &t) {
using namespace theme::ha;
const auto d = t.domain();
if (d == "binary_sensor") return alarm_class(t.device_class) ? RED : AMBER;
if (d == "light" || d == "switch" || d == "input_boolean" || d == "script" || d == "timer" || d == "camera") return AMBER;
if (d == "climate") { const uint32_t c = mode_color(t.state); return c == GREY ? AMBER : c; }
if (d == "vacuum") return t.state == "error" ? RED : TEAL;
if (d == "fan") return CYAN;
if (d == "cover" || d == "scene") return PURPLE;
if (d == "media_player") return LIGHT_BLUE;
if (d == "select" || d == "input_select") return INDIGO;
if (d == "number" || d == "input_number") return TEAL;
if (d == "weather") return weather_color(t.state);
if (d == "sun") return t.state == "above_horizon" ? AMBER : INDIGO;
// At home green; in another zone blue (--state-person-active-color).
if (d == "person") return t.state == "home" ? GREEN : BLUE;
if (d == "sensor") {
// A battery by its charge, as Home Assistant's battery_color.ts: green from 70 %, orange from 30 %, red below.
if (t.device_class == "battery") {
char *end = nullptr;
const float charge = std::strtof(t.state.c_str(), &end);
if (end != t.state.c_str() && *end == '\0' && std::isfinite(charge)) return charge >= 70 ? GREEN : charge >= 30 ? ORANGE : RED;
}
if (t.unit == "lx") return AMBER;
if (t.unit == "°C" || t.unit == "°F") return DEEP_ORANGE;
if (t.unit == "kWh" || t.unit == "Wh") return PURPLE;
if (t.unit == "%") return TEAL;
}
return BLUE;
}
inline const char *mode_icon(const std::string &mode) {
if (mode == "off") return glyph::POWER;
if (mode == "heat") return glyph::FIRE;