ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
homeassistant_light.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5
8#include "../homeassistant.h"
10#include "esphome/core/log.h"
13
14namespace esphome::homeassistant {
15
16ESPHOME_LOG_TAG(TAG, "homeassistant.light");
17
18static constexpr uint32_t ECHO_TIMEOUT_ID = 0;
19
20using namespace esphome::light;
21
22namespace {
23
24// Home Assistant's color mode names, in flash on the ESP8266. The names from "hs" on are every
25// color mode, all handled as RGB: Home Assistant reports rgb_color for all of them and derives
26// the white channels from rgb_color itself.
27PROGMEM_STRING_TABLE(ColorModeNames, "onoff", "brightness", "white", "color_temp", "hs", "xy", "rgb", "rgbw", "rgbww");
28const uint8_t COLOR_MODES[] PROGMEM = {
29 static_cast<uint8_t>(ColorMode::ON_OFF),
30 static_cast<uint8_t>(ColorMode::BRIGHTNESS),
31 static_cast<uint8_t>(ColorMode::WHITE),
32 static_cast<uint8_t>(ColorMode::COLOR_TEMPERATURE),
33};
34
35optional<ColorMode> parse_color_mode(StringRef name) {
36 const int16_t i = progmem_string_table_find<ColorModeNames>(name.c_str(), name.size());
37 if (i < 0) {
38 return nullopt;
39 }
40 return i < sizeof(COLOR_MODES) ? static_cast<ColorMode>(progmem_read_byte(&COLOR_MODES[i])) : ColorMode::RGB;
41}
42
44bool parse_rgb(const char *text, std::array<uint8_t, 3> &values) {
45 for (auto &value : values) {
46 while (*text == ' ' || *text == ',' || *text == '(') {
47 text++;
48 }
49 if (*text < '0' || *text > '9') {
50 return false;
51 }
52 uint16_t number = 0;
53 while (*text >= '0' && *text <= '9') {
54 number = number * 10 + (*text++ - '0');
55 if (number > 255) {
56 return false;
57 }
58 }
59 value = static_cast<uint8_t>(number);
60 }
61 while (*text == ' ' || *text == ',' || *text == ')') {
62 text++;
63 }
64 return *text == '\0';
65}
66
67} // namespace
68
70 auto *server = api::global_api_server;
71 // Home Assistant sends these on subscribe and when they change, so a light that comes up late gets them
72 server->subscribe_home_assistant_state(this->entity_id_, "supported_color_modes", [this](StringRef value) {
73 if (is_unset(value)) {
74 return; // the light is unavailable; its traits stay
75 }
76 // A Python list of the mode names, as strings or as the enum members
77 ColorModeMask modes;
78 char name[ColorModeNames::MAX_LENGTH + 1];
79 size_t length = 0;
80 const bool valid = parse_python_list(
81 value,
82 [&](char c) {
83 if (length < sizeof(name)) {
84 name[length] = c;
85 }
86 length++;
87 },
88 [&]() {
89 auto mode = length <= ColorModeNames::MAX_LENGTH ? parse_color_mode(StringRef(name, length)) : nullopt;
90 if (mode.has_value()) {
91 modes.insert(*mode);
92 } else {
93 ESP_LOGW(TAG, "'%s': Unknown color mode in '%s'", this->entity_id_, value.c_str());
94 }
95 length = 0;
96 });
97 if (!valid) {
98 ESP_LOGW(TAG, "'%s': Can't parse 'supported_color_modes' value '%s'", this->entity_id_, value.c_str());
99 return;
100 }
101 if (modes.empty()) {
102 ESP_LOGW(TAG, "'%s': No color mode found in '%s', keeping the current ones", this->entity_id_, value.c_str());
103 return;
104 }
105 ESP_LOGD(TAG, "'%s': Supported color modes retrieved: %s", this->entity_id_, value.c_str());
107 // Attributes that arrived before the traits were validated against empty ones; apply them again
108 this->schedule_apply_();
109 });
110 // Home Assistant reports the temperature range in Kelvin, the light core works in mireds
111 server->subscribe_home_assistant_state(this->entity_id_, "min_color_temp_kelvin", [this](StringRef value) {
112 if (auto kelvin = this->parse_integer_("min_color_temp_kelvin", value, UINT16_MAX)) {
113 this->traits_.set_max_mireds(1e6f / *kelvin);
114 this->schedule_apply_();
115 }
116 });
117 server->subscribe_home_assistant_state(this->entity_id_, "max_color_temp_kelvin", [this](StringRef value) {
118 if (auto kelvin = this->parse_integer_("max_color_temp_kelvin", value, UINT16_MAX)) {
119 this->traits_.set_min_mireds(1e6f / *kelvin);
120 this->schedule_apply_();
121 }
122 });
123
124 // An attribute is "None" while the light is off or does not report it; the last value stays
125 server->subscribe_home_assistant_state(this->entity_id_, nullptr, [this](StringRef value) {
126 if (is_unset(value)) {
127 return; // normal while Home Assistant or the light is starting up or offline
128 }
129 auto on_off = parse_on_off(value.c_str());
130 if (on_off != PARSE_ON && on_off != PARSE_OFF) {
131 ESP_LOGW(TAG, "'%s': Can't parse '%s' as state!", this->entity_id_, value.c_str());
132 return;
133 }
134 this->import_(this->imported_.state, optional<bool>(on_off == PARSE_ON));
135 });
136 server->subscribe_home_assistant_state(this->entity_id_, "color_mode", [this](StringRef value) {
137 if (is_unset(value)) {
138 return;
139 }
140 auto mode = parse_color_mode(value);
141 if (!mode.has_value()) {
142 ESP_LOGE(TAG, "'%s': Can't parse 'color_mode' value '%s' as color mode!", this->entity_id_, value.c_str());
143 return;
144 }
145 this->import_(this->imported_.color_mode, *mode);
146 });
147 server->subscribe_home_assistant_state(this->entity_id_, "brightness", [this](StringRef value) {
148 if (auto brightness = this->parse_integer_("brightness", value, 255)) {
149 this->import_(this->imported_.brightness, static_cast<uint8_t>(*brightness));
150 }
151 });
152 server->subscribe_home_assistant_state(this->entity_id_, "color_temp_kelvin", [this](StringRef value) {
153 if (auto kelvin = this->parse_integer_("color_temp_kelvin", value, UINT16_MAX)) {
154 this->import_(this->imported_.color_temp_kelvin, *kelvin);
155 }
156 });
157 server->subscribe_home_assistant_state(this->entity_id_, "rgb_color", [this](StringRef value) {
158 if (is_unset(value)) {
159 return;
160 }
161 std::array<uint8_t, 3> rgb;
162 if (!parse_rgb(value.c_str(), rgb)) {
163 ESP_LOGE(TAG, "'%s': Can't parse 'rgb_color' value '%s'!", this->entity_id_, value.c_str());
164 return;
165 }
166 this->on_report_(!this->imported_.has_rgb || this->imported_.rgb != rgb);
167 this->imported_.rgb = rgb;
168 this->imported_.has_rgb = true;
169 });
170}
171
173 ESP_LOGCONFIG(TAG,
174 "Homeassistant Light:\n"
175 " Entity ID: '%s'",
176 this->entity_id_);
177}
178
179optional<uint16_t> HomeassistantLight::parse_integer_(const char *name, StringRef value, uint16_t max) {
180 if (is_unset(value)) {
181 return nullopt;
182 }
183 auto number = parse_number<float>(value.c_str());
184 if (!number.has_value()) {
185 ESP_LOGE(TAG, "'%s': Can't convert '%s' value '%s' to number!", this->entity_id_, name, value.c_str());
186 return nullopt;
187 }
188 // Written so that NaN fails too
189 const bool in_range = *number >= 1.0f && *number <= max;
190 if (!in_range) {
191 ESP_LOGW(TAG, "'%s': '%s' value %.0f is out of range!", this->entity_id_, name, *number);
192 return nullopt;
193 }
194 return static_cast<uint16_t>(*number);
195}
196
198 // A report that changed an attribute answers the change that armed the echo timeout; a repeated
199 // value (or a trait) does not, so a refused change still gets its warning
200 if (changed) {
201 if (this->echo_pending_) {
202 this->echo_pending_ = false;
203 this->cancel_timeout(ECHO_TIMEOUT_ID);
204 }
205 // Noted as the report arrives, not when the deferred apply runs: a call read from the same loop
206 // pass before this report is older than it, one read after it is newer
207 this->state_->clear_called();
208 }
209 this->schedule_apply_();
210}
211
213 // One update changes several attributes, each arriving as its own message; apply them as one call
214 if (!this->apply_scheduled_) {
215 this->apply_scheduled_ = true;
216 this->defer([this]() { this->apply_imported_(); });
217 }
218}
219
221 this->apply_scheduled_ = false;
222 // Nothing to apply before the first state; while a sent change waits for its report the reported
223 // state is the old one, so a trait that arrives then does not bring it back
224 if (!this->imported_.state.has_value() || this->echo_pending_) {
225 return;
226 }
227 // Attributes Home Assistant has not reported keep what the light has. An "off" report carries
228 // no levels, so the ones reported last stay with the light: a turn_on without a brightness
229 // then sends what Home Assistant last had, not a level that never reached it
230 const Attributes &imported = this->imported_;
231 LightColorValues values = this->state_->remote_values;
232 values.set_state(*imported.state);
233 if (imported.color_mode != ColorMode::UNKNOWN) {
234 values.set_color_mode(imported.color_mode);
235 }
236 if (imported.brightness != 0) {
237 values.set_brightness(imported.brightness / 255.0f);
238 }
239 // Home Assistant reports rgb_color in every mode and keeps the attributes of the other
240 // modes, so only the ones of the mode it reports are taken over
241 const ColorMode mode = values.get_color_mode();
242 if (mode == ColorMode::COLOR_TEMPERATURE) {
243 values.set_color_temperature_kelvin(imported.color_temp_kelvin); // ignores an unreported 0
244 } else if (mode == ColorMode::RGB) {
245 if (imported.has_rgb) {
246 values.set_red(imported.rgb[0] / 255.0f);
247 values.set_green(imported.rgb[1] / 255.0f);
248 values.set_blue(imported.rgb[2] / 255.0f);
249 }
250 values.set_color_brightness(1.0f); // brightness is the only level Home Assistant has
251 } else if (mode == ColorMode::WHITE) {
252 values.set_white(1.0f);
253 }
254 if (!this->state_->set_external_state(values)) {
255 // A report that changed something during a transition or flash is applied once it ends, in
256 // place of the values it ends at, unless a call came after the report; a trait or a repeated
257 // report has nothing newer to say
258 if (this->state_->is_transitioning() && !this->state_->called_since_clear()) {
259 this->report_held_ = true;
260 return;
261 }
262 // A local change still waits for LightState::loop() to send it. Home Assistant reports again
263 // once that change reaches it; when it does not, the echo timeout write_state() arms brings the
264 // light back to what was reported last
265 ESP_LOGD(TAG, "'%s': Report dropped, a local change is on its way", this->entity_id_);
266 return;
267 }
268 ESP_LOGV(TAG, "'%s': Took over the state reported by Home Assistant", this->entity_id_);
269}
270
272 // The light core's setup performs one call for the initial state; a second one before the first
273 // write (an on_boot automation) is a local change merged into that write, so it must be sent
274 if (this->initial_write_) {
275 if (this->initial_seen_) {
276 this->initial_write_ = false;
277 }
278 this->initial_seen_ = true;
279 }
280 // Every call that changes the light reaches here within a loop pass (a new transformer's first
281 // apply() or set_immediately_()), and a call since the held report was read is newer than it
282 if (this->report_held_ && state->called_since_clear()) {
283 this->report_held_ = false;
284 }
285}
286
287bool HomeassistantLight::is_imported_(const Attributes &outgoing) const {
288 const Attributes &imported = this->imported_;
289 if (!outgoing.state.has_value() || imported.state != outgoing.state) {
290 return false;
291 }
292 if (!*outgoing.state) {
293 return true;
294 }
295 if (imported.color_mode != outgoing.color_mode || imported.brightness != outgoing.brightness) {
296 return false;
297 }
298 // rgb_color stays reported in the other modes, so only the attributes of the mode count
299 switch (outgoing.color_mode) {
300 case ColorMode::COLOR_TEMPERATURE:
301 return imported.color_temp_kelvin == outgoing.color_temp_kelvin;
302 case ColorMode::RGB:
303 return imported.has_rgb && imported.rgb == outgoing.rgb;
304 default:
305 return true;
306 }
307}
308
310 // Back to the state Home Assistant reported, which the light still has
311 if (!this->imported_.state.has_value()) {
312 ESP_LOGW(TAG, "'%s': Nothing reported yet to fall back to, the light keeps the unsent change", this->entity_id_);
313 return;
314 }
315 this->schedule_apply_();
316}
317
319 // A reported state is taken over without a write, so every write is a local change, except
320 // the very first: the light core writes its initial state, which Home Assistant does not need
321 if (this->initial_write_) {
322 this->initial_write_ = false;
323 return;
324 }
325 if (state->is_transitioning()) {
326 // A transition or flash runs here alone; Home Assistant gets the state it ends at
327 this->transitioned_ = true;
328 return;
329 }
330 const bool held = this->report_held_;
331 const bool transitioned = this->transitioned_;
332 this->report_held_ = false;
333 this->transitioned_ = false;
334 // Home Assistant changed meanwhile; its report is newer than what the transition ends at or
335 // what the flash goes back to
336 if (held) {
337 ESP_LOGD(TAG, "'%s': Applying the report that arrived during the transition", this->entity_id_);
338 this->schedule_apply_();
339 return;
340 }
341 // What Home Assistant holds after the change. It has one level, so the channel brightness of the
342 // mode folds into it
343 const LightColorValues &values = state->current_values;
344 Attributes outgoing;
345 outgoing.state = values.is_on();
346 if (values.is_on()) {
347 outgoing.color_mode = values.get_color_mode();
348 switch (outgoing.color_mode) {
349 case ColorMode::ON_OFF:
350 break;
351 case ColorMode::WHITE:
352 outgoing.brightness = to_uint8_scale(values.get_brightness() * values.get_white());
353 break;
354 case ColorMode::COLOR_TEMPERATURE:
355 outgoing.brightness = to_uint8_scale(values.get_brightness());
356 if (values.get_color_temperature() > 0.0f) {
357 // A mired value below the reported range could give more than 65535 K
358 outgoing.color_temp_kelvin = lroundf(std::min(values.get_color_temperature_kelvin(), 65535.0f));
359 }
360 break;
361 case ColorMode::RGB:
362 outgoing.brightness = to_uint8_scale(values.get_brightness() * values.get_color_brightness());
363 outgoing.rgb = {to_uint8_scale(values.get_red()), to_uint8_scale(values.get_green()),
364 to_uint8_scale(values.get_blue())};
365 outgoing.has_rgb = true;
366 break;
367 default:
368 outgoing.brightness = to_uint8_scale(values.get_brightness());
369 break;
370 }
371 }
372 // Home Assistant reports only what changed, so a change that leaves it as it is gets no report:
373 // nothing to send and no echo to wait for. A change still on its way may alter it first, though
374 if (!this->echo_pending_ && this->is_imported_(outgoing)) {
375 if (transitioned) {
376 // A flash ends where it began, and Home Assistant only ever gets the end
377 ESP_LOGW(TAG, "'%s': Ended at the state Home Assistant already has, nothing to send (a flash never is)",
378 this->entity_id_);
379 } else {
380 ESP_LOGD(TAG, "'%s': Home Assistant already has this state", this->entity_id_);
381 }
382 return;
383 }
384 if (!api::global_api_server->is_connected()) {
385 ESP_LOGE(TAG, "'%s': No clients connected to API server, change lost", this->entity_id_);
386 this->fall_back_();
387 return;
388 }
389#ifdef USE_API_WIZARD_LINKED_INPUTS
390 if (this->entity_id_[0] == '\0') {
391 ESP_LOGW(TAG, "'%s': No entity ID set yet", LOG_STR_ARG(state->get_log_name()));
392 this->fall_back_();
393 return;
394 }
395#endif
396
397 // In flash on the ESP8266 and encoded from there
398 static const char SERVICE_TURN_ON[] PROGMEM = "light.turn_on";
399 static const char SERVICE_TURN_OFF[] PROGMEM = "light.turn_off";
400 static const char ENTITY_ID_KEY[] PROGMEM = "entity_id";
401 static const char BRIGHTNESS_KEY[] PROGMEM = "brightness";
402 static const char WHITE_KEY[] PROGMEM = "white";
403 static const char COLOR_TEMP_KELVIN_KEY[] PROGMEM = "color_temp_kelvin";
404 static const char RGB_COLOR_KEY[] PROGMEM = "rgb_color";
405
406 // The request refers to these buffers until it is sent below
407 char level_buf[4]; // up to 255
408 char color_temp_buf[6]; // up to 65535
409 char color_buf[16];
410
412 resp.data.init(3);
413 auto &entity_id = resp.data.emplace_back();
414 entity_id.key = ProgmemStringRef::from_lit(ENTITY_ID_KEY);
415 entity_id.value = ProgmemStringRef(StringRef(this->entity_id_));
416
417 auto add_number = [&](ProgmemStringRef key, char *buf, size_t size, unsigned number) {
418 auto &entry = resp.data.emplace_back();
419 entry.key = key;
420 entry.value = ProgmemStringRef(StringRef(buf, buf_append_printf(buf, size, 0, "%u", number)));
421 };
422 if (!*outgoing.state) {
423 resp.service = ProgmemStringRef::from_lit(SERVICE_TURN_OFF);
424 } else {
425 resp.service = ProgmemStringRef::from_lit(SERVICE_TURN_ON);
426 if (outgoing.color_mode != ColorMode::ON_OFF) {
427 // The level goes under "white" in that mode, which also selects the mode
428 add_number(outgoing.color_mode == ColorMode::WHITE ? ProgmemStringRef::from_lit(WHITE_KEY)
429 : ProgmemStringRef::from_lit(BRIGHTNESS_KEY),
430 level_buf, sizeof(level_buf), outgoing.brightness);
431 }
432 // Home Assistant converts the color to the light's own channels, which loses the white balance
433 // of a cold/warm white light, so the color goes only when it or the mode changed (or may have,
434 // while a change still waits for its report)
435 const Attributes &imported = this->imported_;
436 const bool new_color = this->echo_pending_ || outgoing.color_mode != imported.color_mode;
437 if (outgoing.color_temp_kelvin != 0 && (new_color || outgoing.color_temp_kelvin != imported.color_temp_kelvin)) {
438 add_number(ProgmemStringRef::from_lit(COLOR_TEMP_KELVIN_KEY), color_temp_buf, sizeof(color_temp_buf),
439 outgoing.color_temp_kelvin);
440 }
441 if (outgoing.has_rgb && (new_color || !imported.has_rgb || outgoing.rgb != imported.rgb)) {
442 // A list, which only a template renders as one
443 resp.data_template.init(1);
444 auto &color = resp.data_template.emplace_back();
445 color.key = ProgmemStringRef::from_lit(RGB_COLOR_KEY);
446 color.value =
447 ProgmemStringRef(StringRef(color_buf, buf_append_printf(color_buf, sizeof(color_buf), 0, "[%u,%u,%u]",
448 outgoing.rgb[0], outgoing.rgb[1], outgoing.rgb[2])));
449 }
450 }
452 // Home Assistant reports the change back in a moment, which cancels this; when it does not, the
453 // change got lost on the way (no subscriber yet, the entity unavailable, the action refused)
454 this->echo_pending_ = true;
455 this->set_timeout(ECHO_TIMEOUT_ID, this->report_timeout_s_ * 1000u, [this]() {
456 this->echo_pending_ = false;
457 ESP_LOGW(TAG, "'%s': Home Assistant did not report the change back", this->entity_id_);
458 this->fall_back_();
459 });
460}
461
462} // namespace esphome::homeassistant
BedjetMode mode
BedJet operating mode.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
constexpr void insert(ValueType value)
Add a single value to the set (std::set compatibility)
constexpr bool empty() const
Check if the set is empty.
A string that may live in flash (on ESP8266): pointer and length, with no byte-reading operations.
Definition string_ref.h:230
static constexpr ProgmemStringRef from_lit(const char(&s)[N])
A PROGMEM char array, with its length known at compile time.
Definition string_ref.h:236
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
constexpr const char * c_str() const
Definition string_ref.h:78
constexpr size_type size() const
Definition string_ref.h:79
void send_homeassistant_action(const HomeassistantActionRequest &call)
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function< void(StringRef)> &&f)
FixedVector< HomeassistantServiceMap > data
Definition api_pb2.h:1423
FixedVector< HomeassistantServiceMap > data_template
Definition api_pb2.h:1424
void fall_back_()
Returns the light to the reported state after a change that did not reach Home Assistant.
optional< uint16_t > parse_integer_(const char *name, StringRef value, uint16_t max)
Parses a Home Assistant attribute as an integer from 1 to max, logging anything else.
void on_report_(bool changed)
A state or attribute report: answers a pending echo when it changed something, and schedules the appl...
void schedule_apply_()
Schedules apply_imported_() once for the attributes of one update.
void write_state(light::LightState *state) override
void update_state(light::LightState *state) override
void import_(T &field, const T &value)
Takes over a reported attribute, reporting whether it changed.
bool is_imported_(const Attributes &outgoing) const
True when Home Assistant reported exactly what a change would make of it.
This class represents the color state for a light object.
void set_color_mode(ColorMode color_mode)
Set the color mode of these light color values.
float get_brightness() const
Get the brightness property of these light color values. In range 0.0 to 1.0.
float get_blue() const
Get the blue property of these light color values. In range 0.0 to 1.0.
float get_white() const
Get the white property of these light color values. In range 0.0 to 1.0.
float get_color_temperature() const
Get the color temperature property of these light color values in mired.
void set_brightness(float brightness)
Set the brightness property of these light color values. In range 0.0 to 1.0.
void set_blue(float blue)
Set the blue property of these light color values. In range 0.0 to 1.0.
void set_color_brightness(float brightness)
Set the color brightness property of these light color values. In range 0.0 to 1.0.
void set_color_temperature_kelvin(float color_temperature)
Set the color temperature property of these light color values in kelvin.
bool is_on() const
Get the binary true/false state of these light color values.
float get_green() const
Get the green property of these light color values. In range 0.0 to 1.0.
void set_state(float state)
Set the state of these light color values. In range from 0.0 (off) to 1.0 (on)
ColorMode get_color_mode() const
Get the color mode of these light color values.
void set_white(float white)
Set the white property of these light color values. In range 0.0 to 1.0.
float get_red() const
Get the red property of these light color values. In range 0.0 to 1.0.
void set_green(float green)
Set the green property of these light color values. In range 0.0 to 1.0.
void set_red(float red)
Set the red property of these light color values. In range 0.0 to 1.0.
float get_color_brightness() const
Get the color brightness property of these light color values. In range 0.0 to 1.0.
float get_color_temperature_kelvin() const
Get the color temperature property of these light color values in kelvin.
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:68
LightColorValues remote_values
The remote color values reported to the frontend.
bool is_transitioning() const
True from the call that starts a transition or flash until it reaches its target.
Definition light_state.h:92
bool set_external_state(const LightColorValues &values)
Take over the state of a light mirrored from elsewhere: current and target values at once,...
bool called_since_clear() const
True once a call is performed after clear_called(), so an output can tell whether a call came after s...
Definition light_state.h:96
void set_supported_color_modes(ColorModeMask supported_color_modes)
void set_min_mireds(float min_mireds)
void set_max_mireds(float max_mireds)
const char * key
bool state
Definition fan.h:2
APIServer * global_api_server
bool parse_python_list(StringRef src, C &&on_char, O &&on_item)
Walk a list attribute as Home Assistant sends it: the Python repr of a list of strings,...
bool is_unset(StringRef value)
True for what Home Assistant reports when there is no value: "None" for an attribute that is not set,...
PROGMEM_STRING_TABLE(UnsetValues, "None", "unavailable", "unknown")
ESPHOME_LOG_TAG(TAG, "homeassistant.binary_sensor")
constexpr ColorMode COLOR_MODE_LOOKUP[] PROGMEM
Definition color_mode.h:114
ColorMode
Color modes are a combination of color capabilities that can be used at the same time.
Definition color_mode.h:50
@ RGB
Color can be controlled using RGB format (includes a brightness control for the color).
value[LAYOUT.day_index()]
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off)
Parse a string that contains either on, off or toggle.
Definition helpers.cpp:554
uint16_t size
Definition helpers.cpp:34
int16_t progmem_string_table_find(const char *value, size_t len)
The index of the string value (len bytes, in RAM) in a PROGMEM_STRING_TABLE, or -1 when it is not one...
Definition progmem.h:160
optional< T > parse_number(const char *str)
Parse an unsigned decimal number from a null-terminated string.
Definition helpers.h:1352
@ PARSE_ON
Definition helpers.h:1741
@ PARSE_OFF
Definition helpers.h:1742
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:51
bool valid
What Home Assistant reported last, in its own units.