ESPHome 2026.1.0-dev
Loading...
Searching...
No Matches
web_server.cpp
Go to the documentation of this file.
1#include "web_server.h"
2#ifdef USE_WEBSERVER
10#include "esphome/core/log.h"
11#include "esphome/core/util.h"
12
13#if !defined(USE_ESP32) && defined(USE_ARDUINO)
14#include "StreamString.h"
15#endif
16
17#include <cstdlib>
18
19#ifdef USE_LIGHT
21#endif
22
23#ifdef USE_LOGGER
25#endif
26
27#ifdef USE_CLIMATE
29#endif
30
31#ifdef USE_WEBSERVER_LOCAL
32#if USE_WEBSERVER_VERSION == 2
33#include "server_index_v2.h"
34#elif USE_WEBSERVER_VERSION == 3
35#include "server_index_v3.h"
36#endif
37#endif
38
39namespace esphome::web_server {
40
41static const char *const TAG = "web_server";
42
43// Longest: UPDATE AVAILABLE (16 chars + null terminator, rounded up)
44static constexpr size_t PSTR_LOCAL_SIZE = 18;
45#define PSTR_LOCAL(mode_s) ESPHOME_strncpy_P(buf, (ESPHOME_PGM_P) ((mode_s)), PSTR_LOCAL_SIZE - 1)
46
47// Parse URL and return match info
48static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) {
49 UrlMatch match{};
50
51 // URL must start with '/'
52 if (url_len < 2 || url_ptr[0] != '/') {
53 return match;
54 }
55
56 // Skip leading '/'
57 const char *start = url_ptr + 1;
58 const char *end = url_ptr + url_len;
59
60 // Find domain (everything up to next '/' or end)
61 const char *domain_end = (const char *) memchr(start, '/', end - start);
62 if (!domain_end) {
63 // No second slash found - original behavior returns invalid
64 return match;
65 }
66
67 // Set domain
68 match.domain = start;
69 match.domain_len = domain_end - start;
70 match.valid = true;
71
72 if (only_domain) {
73 return match;
74 }
75
76 // Parse ID if present
77 if (domain_end + 1 >= end) {
78 return match; // Nothing after domain slash
79 }
80
81 const char *id_start = domain_end + 1;
82 const char *id_end = (const char *) memchr(id_start, '/', end - id_start);
83
84 if (!id_end) {
85 // No more slashes, entire remaining string is ID
86 match.id = id_start;
87 match.id_len = end - id_start;
88 return match;
89 }
90
91 // Set ID
92 match.id = id_start;
93 match.id_len = id_end - id_start;
94
95 // Parse method if present
96 if (id_end + 1 < end) {
97 match.method = id_end + 1;
98 match.method_len = end - (id_end + 1);
99 }
100
101 return match;
102}
103
104#if !defined(USE_ESP32) && defined(USE_ARDUINO)
105// helper for allowing only unique entries in the queue
107 DeferredEvent item(source, message_generator);
108
109 // Use range-based for loop instead of std::find_if to reduce template instantiation overhead and binary size
110 for (auto &event : this->deferred_queue_) {
111 if (event == item) {
112 return; // Already in queue, no need to update since items are equal
113 }
114 }
115 this->deferred_queue_.push_back(item);
116}
117
119 while (!deferred_queue_.empty()) {
120 DeferredEvent &de = deferred_queue_.front();
121 std::string message = de.message_generator_(web_server_, de.source_);
122 if (this->send(message.c_str(), "state") != DISCARDED) {
123 // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
124 deferred_queue_.erase(deferred_queue_.begin());
125 this->consecutive_send_failures_ = 0; // Reset failure count on successful send
126 } else {
127 // NOTE: Similar logic exists in web_server_idf/web_server_idf.cpp in AsyncEventSourceResponse::process_buffer_()
128 // The implementations differ due to platform-specific APIs (DISCARDED vs HTTPD_SOCK_ERR_TIMEOUT, close() vs
129 // fd_.store(0)), but the failure counting and timeout logic should be kept in sync. If you change this logic,
130 // also update the ESP-IDF implementation.
133 // Too many failures, connection is likely dead
134 ESP_LOGW(TAG, "Closing stuck EventSource connection after %" PRIu16 " failed sends",
136 this->close();
137 this->deferred_queue_.clear();
138 }
139 break;
140 }
141 }
142}
143
149
150void DeferredUpdateEventSource::deferrable_send_state(void *source, const char *event_type,
151 message_generator_t *message_generator) {
152 // Skip if no connected clients to avoid unnecessary deferred queue processing
153 if (this->count() == 0)
154 return;
155
156 // allow all json "details_all" to go through before publishing bare state events, this avoids unnamed entries showing
157 // up in the web GUI and reduces event load during initial connect
158 if (!entities_iterator_.completed() && 0 != strcmp(event_type, "state_detail_all"))
159 return;
160
161 if (source == nullptr)
162 return;
163 if (event_type == nullptr)
164 return;
165 if (message_generator == nullptr)
166 return;
167
168 if (0 != strcmp(event_type, "state_detail_all") && 0 != strcmp(event_type, "state")) {
169 ESP_LOGE(TAG, "Can't defer non-state event");
170 }
171
172 if (!deferred_queue_.empty())
174 if (!deferred_queue_.empty()) {
175 // deferred queue still not empty which means downstream event queue full, no point trying to send first
176 deq_push_back_with_dedup_(source, message_generator);
177 } else {
178 std::string message = message_generator(web_server_, source);
179 if (this->send(message.c_str(), "state") == DISCARDED) {
180 deq_push_back_with_dedup_(source, message_generator);
181 } else {
182 this->consecutive_send_failures_ = 0; // Reset failure count on successful send
183 }
184 }
185}
186
187// used for logs plus the initial ping/config
188void DeferredUpdateEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id,
189 uint32_t reconnect) {
190 this->send(message, event, id, reconnect);
191}
192
194 for (DeferredUpdateEventSource *dues : *this) {
195 dues->loop();
196 }
197}
198
199void DeferredUpdateEventSourceList::deferrable_send_state(void *source, const char *event_type,
200 message_generator_t *message_generator) {
201 // Skip if no event sources (no connected clients) to avoid unnecessary iteration
202 if (this->empty())
203 return;
204 for (DeferredUpdateEventSource *dues : *this) {
205 dues->deferrable_send_state(source, event_type, message_generator);
206 }
207}
208
209void DeferredUpdateEventSourceList::try_send_nodefer(const char *message, const char *event, uint32_t id,
210 uint32_t reconnect) {
211 for (DeferredUpdateEventSource *dues : *this) {
212 dues->try_send_nodefer(message, event, id, reconnect);
213 }
214}
215
216void DeferredUpdateEventSourceList::add_new_client(WebServer *ws, AsyncWebServerRequest *request) {
218 this->push_back(es);
219
220 es->onConnect([this, es](AsyncEventSourceClient *client) { this->on_client_connect_(es); });
221
222 es->onDisconnect([this, es](AsyncEventSourceClient *client) { this->on_client_disconnect_(es); });
223
224 es->handleRequest(request);
225}
226
228 WebServer *ws = source->web_server_;
229 ws->defer([ws, source]() {
230 // Configure reconnect timeout and send config
231 // this should always go through since the AsyncEventSourceClient event queue is empty on connect
232 std::string message = ws->get_config_json();
233 source->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
234
235#ifdef USE_WEBSERVER_SORTING
236 for (auto &group : ws->sorting_groups_) {
237 json::JsonBuilder builder;
238 JsonObject root = builder.root();
239 root[ESPHOME_F("name")] = group.second.name;
240 root[ESPHOME_F("sorting_weight")] = group.second.weight;
241 message = builder.serialize();
242
243 // up to 31 groups should be able to be queued initially without defer
244 source->try_send_nodefer(message.c_str(), "sorting_group");
245 }
246#endif
247
249
250 // just dump them all up-front and take advantage of the deferred queue
251 // on second thought that takes too long, but leaving the commented code here for debug purposes
252 // while(!source->entities_iterator_.completed()) {
253 // source->entities_iterator_.advance();
254 //}
255 });
256}
257
259 source->web_server_->defer([this, source]() {
260 // This method was called via WebServer->defer() and is no longer executing in the
261 // context of the network callback. The object is now dead and can be safely deleted.
262 this->remove(source);
263 delete source; // NOLINT
264 });
265}
266#endif
267
269
270#ifdef USE_WEBSERVER_CSS_INCLUDE
271void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; }
272#endif
273#ifdef USE_WEBSERVER_JS_INCLUDE
274void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; }
275#endif
276
278 json::JsonBuilder builder;
279 JsonObject root = builder.root();
280
281 root[ESPHOME_F("title")] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name();
282 char comment_buffer[ESPHOME_COMMENT_SIZE];
283 App.get_comment_string(comment_buffer);
284 root[ESPHOME_F("comment")] = comment_buffer;
285#if defined(USE_WEBSERVER_OTA_DISABLED) || !defined(USE_WEBSERVER_OTA)
286 root[ESPHOME_F("ota")] = false; // Note: USE_WEBSERVER_OTA_DISABLED only affects web_server, not captive_portal
287#else
288 root[ESPHOME_F("ota")] = true;
289#endif
290 root[ESPHOME_F("log")] = this->expose_log_;
291 root[ESPHOME_F("lang")] = "en";
292
293 return builder.serialize();
294}
295
298 this->base_->init();
299
300#ifdef USE_LOGGER
301 if (logger::global_logger != nullptr && this->expose_log_) {
303 }
304#endif
305
306#ifdef USE_ESP32
307 this->base_->add_handler(&this->events_);
308#endif
309 this->base_->add_handler(this);
310
311 // OTA is now handled by the web_server OTA platform
312
313 // doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly
314 // getting a lot of events
315 this->set_interval(10000, [this]() { this->events_.try_send_nodefer("", "ping", millis(), 30000); });
316}
317void WebServer::loop() { this->events_.loop(); }
318
319#ifdef USE_LOGGER
320void WebServer::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
321 (void) level;
322 (void) tag;
323 (void) message_len;
324 this->events_.try_send_nodefer(message, "log", millis());
325}
326#endif
327
329 ESP_LOGCONFIG(TAG,
330 "Web Server:\n"
331 " Address: %s:%u",
333}
335
336#ifdef USE_WEBSERVER_LOCAL
337void WebServer::handle_index_request(AsyncWebServerRequest *request) {
338#ifndef USE_ESP8266
339 AsyncWebServerResponse *response = request->beginResponse(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
340#else
341 AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
342#endif
343 response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
344 request->send(response);
345}
346#elif USE_WEBSERVER_VERSION >= 2
347void WebServer::handle_index_request(AsyncWebServerRequest *request) {
348#ifndef USE_ESP8266
349 AsyncWebServerResponse *response =
350 request->beginResponse(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE);
351#else
352 AsyncWebServerResponse *response =
353 request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE);
354#endif
355 // No gzip header here because the HTML file is so small
356 request->send(response);
357}
358#endif
359
360#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
361void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) {
362 AsyncWebServerResponse *response = request->beginResponse(200, "");
363 response->addHeader(ESPHOME_F("Access-Control-Allow-Private-Network"), ESPHOME_F("true"));
364 response->addHeader(ESPHOME_F("Private-Network-Access-Name"), App.get_name().c_str());
365 char mac_s[18];
366 response->addHeader(ESPHOME_F("Private-Network-Access-ID"), get_mac_address_pretty_into_buffer(mac_s));
367 request->send(response);
368}
369#endif
370
371#ifdef USE_WEBSERVER_CSS_INCLUDE
372void WebServer::handle_css_request(AsyncWebServerRequest *request) {
373#ifndef USE_ESP8266
374 AsyncWebServerResponse *response =
375 request->beginResponse(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE);
376#else
377 AsyncWebServerResponse *response =
378 request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE);
379#endif
380 response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
381 request->send(response);
382}
383#endif
384
385#ifdef USE_WEBSERVER_JS_INCLUDE
386void WebServer::handle_js_request(AsyncWebServerRequest *request) {
387#ifndef USE_ESP8266
388 AsyncWebServerResponse *response =
389 request->beginResponse(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE);
390#else
391 AsyncWebServerResponse *response =
392 request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE);
393#endif
394 response->addHeader(ESPHOME_F("Content-Encoding"), ESPHOME_F("gzip"));
395 request->send(response);
396}
397#endif
398
399// Helper functions to reduce code size by avoiding macro expansion
400static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, JsonDetail start_config) {
401 char id_buf[160]; // prefix + dash + object_id (up to 128) + null
402 size_t len = strlen(prefix);
403 memcpy(id_buf, prefix, len); // NOLINT(bugprone-not-null-terminated-result) - null added by write_object_id_to
404 id_buf[len++] = '-';
405 obj->write_object_id_to(id_buf + len, sizeof(id_buf) - len);
406 root[ESPHOME_F("id")] = id_buf;
407 if (start_config == DETAIL_ALL) {
408 root[ESPHOME_F("name")] = obj->get_name();
409 root[ESPHOME_F("icon")] = obj->get_icon_ref();
410 root[ESPHOME_F("entity_category")] = obj->get_entity_category();
411 bool is_disabled = obj->is_disabled_by_default();
412 if (is_disabled)
413 root[ESPHOME_F("is_disabled_by_default")] = is_disabled;
414 }
415}
416
417// Keep as separate function even though only used once: reduces code size by ~48 bytes
418// by allowing compiler to share code between template instantiations (bool, float, etc.)
419template<typename T>
420static void set_json_value(JsonObject &root, EntityBase *obj, const char *prefix, const T &value,
421 JsonDetail start_config) {
422 set_json_id(root, obj, prefix, start_config);
423 root[ESPHOME_F("value")] = value;
424}
425
426template<typename T>
427static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const char *prefix, const char *state,
428 const T &value, JsonDetail start_config) {
429 set_json_value(root, obj, prefix, value, start_config);
430 root[ESPHOME_F("state")] = state;
431}
432
433// Helper to get request detail parameter
434static JsonDetail get_request_detail(AsyncWebServerRequest *request) {
435 auto *param = request->getParam("detail");
436 return (param && param->value() == "all") ? DETAIL_ALL : DETAIL_STATE;
437}
438
439#ifdef USE_SENSOR
441 if (!this->include_internal_ && obj->is_internal())
442 return;
443 this->events_.deferrable_send_state(obj, "state", sensor_state_json_generator);
444}
445void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
446 for (sensor::Sensor *obj : App.get_sensors()) {
447 if (!match.id_equals_entity(obj))
448 continue;
449 // Note: request->method() is always HTTP_GET here (canHandle ensures this)
450 if (match.method_empty()) {
451 auto detail = get_request_detail(request);
452 std::string data = this->sensor_json_(obj, obj->state, detail);
453 request->send(200, "application/json", data.c_str());
454 return;
455 }
456 }
457 request->send(404);
458}
459std::string WebServer::sensor_state_json_generator(WebServer *web_server, void *source) {
460 return web_server->sensor_json_((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_STATE);
461}
462std::string WebServer::sensor_all_json_generator(WebServer *web_server, void *source) {
463 return web_server->sensor_json_((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_ALL);
464}
465std::string WebServer::sensor_json_(sensor::Sensor *obj, float value, JsonDetail start_config) {
466 json::JsonBuilder builder;
467 JsonObject root = builder.root();
468
469 const auto uom_ref = obj->get_unit_of_measurement_ref();
470 char buf[VALUE_ACCURACY_MAX_LEN];
471 const char *state = std::isnan(value)
472 ? "NA"
473 : (value_accuracy_with_uom_to_buf(buf, value, obj->get_accuracy_decimals(), uom_ref), buf);
474 set_json_icon_state_value(root, obj, "sensor", state, value, start_config);
475 if (start_config == DETAIL_ALL) {
476 this->add_sorting_info_(root, obj);
477 if (!uom_ref.empty())
478 root[ESPHOME_F("uom")] = uom_ref;
479 }
480
481 return builder.serialize();
482}
483#endif
484
485#ifdef USE_TEXT_SENSOR
487 if (!this->include_internal_ && obj->is_internal())
488 return;
489 this->events_.deferrable_send_state(obj, "state", text_sensor_state_json_generator);
490}
491void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
492 for (text_sensor::TextSensor *obj : App.get_text_sensors()) {
493 if (!match.id_equals_entity(obj))
494 continue;
495 // Note: request->method() is always HTTP_GET here (canHandle ensures this)
496 if (match.method_empty()) {
497 auto detail = get_request_detail(request);
498 std::string data = this->text_sensor_json_(obj, obj->state, detail);
499 request->send(200, "application/json", data.c_str());
500 return;
501 }
502 }
503 request->send(404);
504}
505std::string WebServer::text_sensor_state_json_generator(WebServer *web_server, void *source) {
506 return web_server->text_sensor_json_((text_sensor::TextSensor *) (source),
508}
509std::string WebServer::text_sensor_all_json_generator(WebServer *web_server, void *source) {
510 return web_server->text_sensor_json_((text_sensor::TextSensor *) (source),
511 ((text_sensor::TextSensor *) (source))->state, DETAIL_ALL);
512}
513std::string WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std::string &value,
514 JsonDetail start_config) {
515 json::JsonBuilder builder;
516 JsonObject root = builder.root();
517
518 set_json_icon_state_value(root, obj, "text_sensor", value.c_str(), value.c_str(), start_config);
519 if (start_config == DETAIL_ALL) {
520 this->add_sorting_info_(root, obj);
521 }
522
523 return builder.serialize();
524}
525#endif
526
527#ifdef USE_SWITCH
529 if (!this->include_internal_ && obj->is_internal())
530 return;
531 this->events_.deferrable_send_state(obj, "state", switch_state_json_generator);
532}
533void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
534 for (switch_::Switch *obj : App.get_switches()) {
535 if (!match.id_equals_entity(obj))
536 continue;
537
538 if (request->method() == HTTP_GET && match.method_empty()) {
539 auto detail = get_request_detail(request);
540 std::string data = this->switch_json_(obj, obj->state, detail);
541 request->send(200, "application/json", data.c_str());
542 return;
543 }
544
545 // Handle action methods with single defer and response
546 enum SwitchAction { NONE, TOGGLE, TURN_ON, TURN_OFF };
547 SwitchAction action = NONE;
548
549 if (match.method_equals("toggle")) {
550 action = TOGGLE;
551 } else if (match.method_equals("turn_on")) {
552 action = TURN_ON;
553 } else if (match.method_equals("turn_off")) {
554 action = TURN_OFF;
555 }
556
557 if (action != NONE) {
558 this->defer([obj, action]() {
559 switch (action) {
560 case TOGGLE:
561 obj->toggle();
562 break;
563 case TURN_ON:
564 obj->turn_on();
565 break;
566 case TURN_OFF:
567 obj->turn_off();
568 break;
569 default:
570 break;
571 }
572 });
573 request->send(200);
574 } else {
575 request->send(404);
576 }
577 return;
578 }
579 request->send(404);
580}
581std::string WebServer::switch_state_json_generator(WebServer *web_server, void *source) {
582 return web_server->switch_json_((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_STATE);
583}
584std::string WebServer::switch_all_json_generator(WebServer *web_server, void *source) {
585 return web_server->switch_json_((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_ALL);
586}
587std::string WebServer::switch_json_(switch_::Switch *obj, bool value, JsonDetail start_config) {
588 json::JsonBuilder builder;
589 JsonObject root = builder.root();
590
591 set_json_icon_state_value(root, obj, "switch", value ? "ON" : "OFF", value, start_config);
592 if (start_config == DETAIL_ALL) {
593 root[ESPHOME_F("assumed_state")] = obj->assumed_state();
594 this->add_sorting_info_(root, obj);
595 }
596
597 return builder.serialize();
598}
599#endif
600
601#ifdef USE_BUTTON
602void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
603 for (button::Button *obj : App.get_buttons()) {
604 if (!match.id_equals_entity(obj))
605 continue;
606 if (request->method() == HTTP_GET && match.method_empty()) {
607 auto detail = get_request_detail(request);
608 std::string data = this->button_json_(obj, detail);
609 request->send(200, "application/json", data.c_str());
610 } else if (match.method_equals("press")) {
611 this->defer([obj]() { obj->press(); });
612 request->send(200);
613 return;
614 } else {
615 request->send(404);
616 }
617 return;
618 }
619 request->send(404);
620}
621std::string WebServer::button_state_json_generator(WebServer *web_server, void *source) {
622 return web_server->button_json_((button::Button *) (source), DETAIL_STATE);
623}
624std::string WebServer::button_all_json_generator(WebServer *web_server, void *source) {
625 return web_server->button_json_((button::Button *) (source), DETAIL_ALL);
626}
627std::string WebServer::button_json_(button::Button *obj, JsonDetail start_config) {
628 json::JsonBuilder builder;
629 JsonObject root = builder.root();
630
631 set_json_id(root, obj, "button", start_config);
632 if (start_config == DETAIL_ALL) {
633 this->add_sorting_info_(root, obj);
634 }
635
636 return builder.serialize();
637}
638#endif
639
640#ifdef USE_BINARY_SENSOR
642 if (!this->include_internal_ && obj->is_internal())
643 return;
644 this->events_.deferrable_send_state(obj, "state", binary_sensor_state_json_generator);
645}
646void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
648 if (!match.id_equals_entity(obj))
649 continue;
650 // Note: request->method() is always HTTP_GET here (canHandle ensures this)
651 if (match.method_empty()) {
652 auto detail = get_request_detail(request);
653 std::string data = this->binary_sensor_json_(obj, obj->state, detail);
654 request->send(200, "application/json", data.c_str());
655 return;
656 }
657 }
658 request->send(404);
659}
660std::string WebServer::binary_sensor_state_json_generator(WebServer *web_server, void *source) {
661 return web_server->binary_sensor_json_((binary_sensor::BinarySensor *) (source),
663}
664std::string WebServer::binary_sensor_all_json_generator(WebServer *web_server, void *source) {
665 return web_server->binary_sensor_json_((binary_sensor::BinarySensor *) (source),
667}
668std::string WebServer::binary_sensor_json_(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
669 json::JsonBuilder builder;
670 JsonObject root = builder.root();
671
672 set_json_icon_state_value(root, obj, "binary_sensor", value ? "ON" : "OFF", value, start_config);
673 if (start_config == DETAIL_ALL) {
674 this->add_sorting_info_(root, obj);
675 }
676
677 return builder.serialize();
678}
679#endif
680
681#ifdef USE_FAN
683 if (!this->include_internal_ && obj->is_internal())
684 return;
685 this->events_.deferrable_send_state(obj, "state", fan_state_json_generator);
686}
687void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
688 for (fan::Fan *obj : App.get_fans()) {
689 if (!match.id_equals_entity(obj))
690 continue;
691
692 if (request->method() == HTTP_GET && match.method_empty()) {
693 auto detail = get_request_detail(request);
694 std::string data = this->fan_json_(obj, detail);
695 request->send(200, "application/json", data.c_str());
696 } else if (match.method_equals("toggle")) {
697 this->defer([obj]() { obj->toggle().perform(); });
698 request->send(200);
699 } else {
700 bool is_on = match.method_equals("turn_on");
701 bool is_off = match.method_equals("turn_off");
702 if (!is_on && !is_off) {
703 request->send(404);
704 return;
705 }
706 auto call = is_on ? obj->turn_on() : obj->turn_off();
707
708 parse_int_param_(request, "speed_level", call, &decltype(call)::set_speed);
709
710 if (request->hasParam("oscillation")) {
711 auto speed = request->getParam("oscillation")->value();
712 auto val = parse_on_off(speed.c_str());
713 switch (val) {
714 case PARSE_ON:
715 call.set_oscillating(true);
716 break;
717 case PARSE_OFF:
718 call.set_oscillating(false);
719 break;
720 case PARSE_TOGGLE:
721 call.set_oscillating(!obj->oscillating);
722 break;
723 case PARSE_NONE:
724 request->send(404);
725 return;
726 }
727 }
728 this->defer([call]() mutable { call.perform(); });
729 request->send(200);
730 }
731 return;
732 }
733 request->send(404);
734}
735std::string WebServer::fan_state_json_generator(WebServer *web_server, void *source) {
736 return web_server->fan_json_((fan::Fan *) (source), DETAIL_STATE);
737}
738std::string WebServer::fan_all_json_generator(WebServer *web_server, void *source) {
739 return web_server->fan_json_((fan::Fan *) (source), DETAIL_ALL);
740}
741std::string WebServer::fan_json_(fan::Fan *obj, JsonDetail start_config) {
742 json::JsonBuilder builder;
743 JsonObject root = builder.root();
744
745 set_json_icon_state_value(root, obj, "fan", obj->state ? "ON" : "OFF", obj->state, start_config);
746 const auto traits = obj->get_traits();
747 if (traits.supports_speed()) {
748 root[ESPHOME_F("speed_level")] = obj->speed;
749 root[ESPHOME_F("speed_count")] = traits.supported_speed_count();
750 }
751 if (obj->get_traits().supports_oscillation())
752 root[ESPHOME_F("oscillation")] = obj->oscillating;
753 if (start_config == DETAIL_ALL) {
754 this->add_sorting_info_(root, obj);
755 }
756
757 return builder.serialize();
758}
759#endif
760
761#ifdef USE_LIGHT
763 if (!this->include_internal_ && obj->is_internal())
764 return;
765 this->events_.deferrable_send_state(obj, "state", light_state_json_generator);
766}
767void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
768 for (light::LightState *obj : App.get_lights()) {
769 if (!match.id_equals_entity(obj))
770 continue;
771
772 if (request->method() == HTTP_GET && match.method_empty()) {
773 auto detail = get_request_detail(request);
774 std::string data = this->light_json_(obj, detail);
775 request->send(200, "application/json", data.c_str());
776 } else if (match.method_equals("toggle")) {
777 this->defer([obj]() { obj->toggle().perform(); });
778 request->send(200);
779 } else {
780 bool is_on = match.method_equals("turn_on");
781 bool is_off = match.method_equals("turn_off");
782 if (!is_on && !is_off) {
783 request->send(404);
784 return;
785 }
786 auto call = is_on ? obj->turn_on() : obj->turn_off();
787
788 if (is_on) {
789 // Parse color parameters
790 parse_light_param_(request, "brightness", call, &decltype(call)::set_brightness, 255.0f);
791 parse_light_param_(request, "r", call, &decltype(call)::set_red, 255.0f);
792 parse_light_param_(request, "g", call, &decltype(call)::set_green, 255.0f);
793 parse_light_param_(request, "b", call, &decltype(call)::set_blue, 255.0f);
794 parse_light_param_(request, "white_value", call, &decltype(call)::set_white, 255.0f);
795 parse_light_param_(request, "color_temp", call, &decltype(call)::set_color_temperature);
796
797 // Parse timing parameters
798 parse_light_param_uint_(request, "flash", call, &decltype(call)::set_flash_length, 1000);
799 }
800 parse_light_param_uint_(request, "transition", call, &decltype(call)::set_transition_length, 1000);
801
802 if (is_on) {
803 parse_string_param_(request, "effect", call, &decltype(call)::set_effect);
804 }
805
806 this->defer([call]() mutable { call.perform(); });
807 request->send(200);
808 }
809 return;
810 }
811 request->send(404);
812}
813std::string WebServer::light_state_json_generator(WebServer *web_server, void *source) {
814 return web_server->light_json_((light::LightState *) (source), DETAIL_STATE);
815}
816std::string WebServer::light_all_json_generator(WebServer *web_server, void *source) {
817 return web_server->light_json_((light::LightState *) (source), DETAIL_ALL);
818}
819std::string WebServer::light_json_(light::LightState *obj, JsonDetail start_config) {
820 json::JsonBuilder builder;
821 JsonObject root = builder.root();
822
823 set_json_value(root, obj, "light", obj->remote_values.is_on() ? "ON" : "OFF", start_config);
824
826 if (start_config == DETAIL_ALL) {
827 JsonArray opt = root[ESPHOME_F("effects")].to<JsonArray>();
828 opt.add("None");
829 for (auto const &option : obj->get_effects()) {
830 opt.add(option->get_name());
831 }
832 this->add_sorting_info_(root, obj);
833 }
834
835 return builder.serialize();
836}
837#endif
838
839#ifdef USE_COVER
841 if (!this->include_internal_ && obj->is_internal())
842 return;
843 this->events_.deferrable_send_state(obj, "state", cover_state_json_generator);
844}
845void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
846 for (cover::Cover *obj : App.get_covers()) {
847 if (!match.id_equals_entity(obj))
848 continue;
849
850 if (request->method() == HTTP_GET && match.method_empty()) {
851 auto detail = get_request_detail(request);
852 std::string data = this->cover_json_(obj, detail);
853 request->send(200, "application/json", data.c_str());
854 return;
855 }
856
857 auto call = obj->make_call();
858
859 // Lookup table for cover methods
860 static const struct {
861 const char *name;
863 } METHODS[] = {
868 };
869
870 bool found = false;
871 for (const auto &method : METHODS) {
872 if (match.method_equals(method.name)) {
873 (call.*method.action)();
874 found = true;
875 break;
876 }
877 }
878
879 if (!found && !match.method_equals("set")) {
880 request->send(404);
881 return;
882 }
883
884 auto traits = obj->get_traits();
885 if ((request->hasParam("position") && !traits.get_supports_position()) ||
886 (request->hasParam("tilt") && !traits.get_supports_tilt())) {
887 request->send(409);
888 return;
889 }
890
891 parse_float_param_(request, "position", call, &decltype(call)::set_position);
892 parse_float_param_(request, "tilt", call, &decltype(call)::set_tilt);
893
894 this->defer([call]() mutable { call.perform(); });
895 request->send(200);
896 return;
897 }
898 request->send(404);
899}
900std::string WebServer::cover_state_json_generator(WebServer *web_server, void *source) {
901 return web_server->cover_json_((cover::Cover *) (source), DETAIL_STATE);
902}
903std::string WebServer::cover_all_json_generator(WebServer *web_server, void *source) {
904 return web_server->cover_json_((cover::Cover *) (source), DETAIL_ALL);
905}
906std::string WebServer::cover_json_(cover::Cover *obj, JsonDetail start_config) {
907 json::JsonBuilder builder;
908 JsonObject root = builder.root();
909
910 set_json_icon_state_value(root, obj, "cover", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position,
911 start_config);
912 char buf[PSTR_LOCAL_SIZE];
913 root[ESPHOME_F("current_operation")] = PSTR_LOCAL(cover::cover_operation_to_str(obj->current_operation));
914
916 root[ESPHOME_F("position")] = obj->position;
917 if (obj->get_traits().get_supports_tilt())
918 root[ESPHOME_F("tilt")] = obj->tilt;
919 if (start_config == DETAIL_ALL) {
920 this->add_sorting_info_(root, obj);
921 }
922
923 return builder.serialize();
924}
925#endif
926
927#ifdef USE_NUMBER
929 if (!this->include_internal_ && obj->is_internal())
930 return;
931 this->events_.deferrable_send_state(obj, "state", number_state_json_generator);
932}
933void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
934 for (auto *obj : App.get_numbers()) {
935 if (!match.id_equals_entity(obj))
936 continue;
937
938 if (request->method() == HTTP_GET && match.method_empty()) {
939 auto detail = get_request_detail(request);
940 std::string data = this->number_json_(obj, obj->state, detail);
941 request->send(200, "application/json", data.c_str());
942 return;
943 }
944 if (!match.method_equals("set")) {
945 request->send(404);
946 return;
947 }
948
949 auto call = obj->make_call();
950 parse_float_param_(request, "value", call, &decltype(call)::set_value);
951
952 this->defer([call]() mutable { call.perform(); });
953 request->send(200);
954 return;
955 }
956 request->send(404);
957}
958
959std::string WebServer::number_state_json_generator(WebServer *web_server, void *source) {
960 return web_server->number_json_((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_STATE);
961}
962std::string WebServer::number_all_json_generator(WebServer *web_server, void *source) {
963 return web_server->number_json_((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_ALL);
964}
965std::string WebServer::number_json_(number::Number *obj, float value, JsonDetail start_config) {
966 json::JsonBuilder builder;
967 JsonObject root = builder.root();
968
969 const auto uom_ref = obj->traits.get_unit_of_measurement_ref();
970 const int8_t accuracy = step_to_accuracy_decimals(obj->traits.get_step());
971
972 // Need two buffers: one for value, one for state with UOM
973 char val_buf[VALUE_ACCURACY_MAX_LEN];
974 char state_buf[VALUE_ACCURACY_MAX_LEN];
975 const char *val_str = std::isnan(value) ? "\"NaN\"" : (value_accuracy_to_buf(val_buf, value, accuracy), val_buf);
976 const char *state_str =
977 std::isnan(value) ? "NA" : (value_accuracy_with_uom_to_buf(state_buf, value, accuracy, uom_ref), state_buf);
978 set_json_icon_state_value(root, obj, "number", state_str, val_str, start_config);
979 if (start_config == DETAIL_ALL) {
980 // ArduinoJson copies the string immediately, so we can reuse val_buf
981 root[ESPHOME_F("min_value")] = (value_accuracy_to_buf(val_buf, obj->traits.get_min_value(), accuracy), val_buf);
982 root[ESPHOME_F("max_value")] = (value_accuracy_to_buf(val_buf, obj->traits.get_max_value(), accuracy), val_buf);
983 root[ESPHOME_F("step")] = (value_accuracy_to_buf(val_buf, obj->traits.get_step(), accuracy), val_buf);
984 root[ESPHOME_F("mode")] = (int) obj->traits.get_mode();
985 if (!uom_ref.empty())
986 root[ESPHOME_F("uom")] = uom_ref;
987 this->add_sorting_info_(root, obj);
988 }
989
990 return builder.serialize();
991}
992#endif
993
994#ifdef USE_DATETIME_DATE
996 if (!this->include_internal_ && obj->is_internal())
997 return;
998 this->events_.deferrable_send_state(obj, "state", date_state_json_generator);
999}
1000void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1001 for (auto *obj : App.get_dates()) {
1002 if (!match.id_equals_entity(obj))
1003 continue;
1004 if (request->method() == HTTP_GET && match.method_empty()) {
1005 auto detail = get_request_detail(request);
1006 std::string data = this->date_json_(obj, detail);
1007 request->send(200, "application/json", data.c_str());
1008 return;
1009 }
1010 if (!match.method_equals("set")) {
1011 request->send(404);
1012 return;
1013 }
1014
1015 auto call = obj->make_call();
1016
1017 if (!request->hasParam("value")) {
1018 request->send(409);
1019 return;
1020 }
1021
1022 parse_string_param_(request, "value", call, &decltype(call)::set_date);
1023
1024 this->defer([call]() mutable { call.perform(); });
1025 request->send(200);
1026 return;
1027 }
1028 request->send(404);
1029}
1030
1031std::string WebServer::date_state_json_generator(WebServer *web_server, void *source) {
1032 return web_server->date_json_((datetime::DateEntity *) (source), DETAIL_STATE);
1033}
1034std::string WebServer::date_all_json_generator(WebServer *web_server, void *source) {
1035 return web_server->date_json_((datetime::DateEntity *) (source), DETAIL_ALL);
1036}
1037std::string WebServer::date_json_(datetime::DateEntity *obj, JsonDetail start_config) {
1038 json::JsonBuilder builder;
1039 JsonObject root = builder.root();
1040
1041 // Format: YYYY-MM-DD (max 10 chars + null)
1042 char value[12];
1043#ifdef USE_ESP8266
1044 snprintf_P(value, sizeof(value), PSTR("%d-%02d-%02d"), obj->year, obj->month, obj->day);
1045#else
1046 snprintf(value, sizeof(value), "%d-%02d-%02d", obj->year, obj->month, obj->day);
1047#endif
1048 set_json_icon_state_value(root, obj, "date", value, value, start_config);
1049 if (start_config == DETAIL_ALL) {
1050 this->add_sorting_info_(root, obj);
1051 }
1052
1053 return builder.serialize();
1054}
1055#endif // USE_DATETIME_DATE
1056
1057#ifdef USE_DATETIME_TIME
1059 if (!this->include_internal_ && obj->is_internal())
1060 return;
1061 this->events_.deferrable_send_state(obj, "state", time_state_json_generator);
1062}
1063void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1064 for (auto *obj : App.get_times()) {
1065 if (!match.id_equals_entity(obj))
1066 continue;
1067 if (request->method() == HTTP_GET && match.method_empty()) {
1068 auto detail = get_request_detail(request);
1069 std::string data = this->time_json_(obj, detail);
1070 request->send(200, "application/json", data.c_str());
1071 return;
1072 }
1073 if (!match.method_equals("set")) {
1074 request->send(404);
1075 return;
1076 }
1077
1078 auto call = obj->make_call();
1079
1080 if (!request->hasParam("value")) {
1081 request->send(409);
1082 return;
1083 }
1084
1085 parse_string_param_(request, "value", call, &decltype(call)::set_time);
1086
1087 this->defer([call]() mutable { call.perform(); });
1088 request->send(200);
1089 return;
1090 }
1091 request->send(404);
1092}
1093std::string WebServer::time_state_json_generator(WebServer *web_server, void *source) {
1094 return web_server->time_json_((datetime::TimeEntity *) (source), DETAIL_STATE);
1095}
1096std::string WebServer::time_all_json_generator(WebServer *web_server, void *source) {
1097 return web_server->time_json_((datetime::TimeEntity *) (source), DETAIL_ALL);
1098}
1099std::string WebServer::time_json_(datetime::TimeEntity *obj, JsonDetail start_config) {
1100 json::JsonBuilder builder;
1101 JsonObject root = builder.root();
1102
1103 // Format: HH:MM:SS (8 chars + null)
1104 char value[12];
1105#ifdef USE_ESP8266
1106 snprintf_P(value, sizeof(value), PSTR("%02d:%02d:%02d"), obj->hour, obj->minute, obj->second);
1107#else
1108 snprintf(value, sizeof(value), "%02d:%02d:%02d", obj->hour, obj->minute, obj->second);
1109#endif
1110 set_json_icon_state_value(root, obj, "time", value, value, start_config);
1111 if (start_config == DETAIL_ALL) {
1112 this->add_sorting_info_(root, obj);
1113 }
1114
1115 return builder.serialize();
1116}
1117#endif // USE_DATETIME_TIME
1118
1119#ifdef USE_DATETIME_DATETIME
1121 if (!this->include_internal_ && obj->is_internal())
1122 return;
1123 this->events_.deferrable_send_state(obj, "state", datetime_state_json_generator);
1124}
1125void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1126 for (auto *obj : App.get_datetimes()) {
1127 if (!match.id_equals_entity(obj))
1128 continue;
1129 if (request->method() == HTTP_GET && match.method_empty()) {
1130 auto detail = get_request_detail(request);
1131 std::string data = this->datetime_json_(obj, detail);
1132 request->send(200, "application/json", data.c_str());
1133 return;
1134 }
1135 if (!match.method_equals("set")) {
1136 request->send(404);
1137 return;
1138 }
1139
1140 auto call = obj->make_call();
1141
1142 if (!request->hasParam("value")) {
1143 request->send(409);
1144 return;
1145 }
1146
1147 parse_string_param_(request, "value", call, &decltype(call)::set_datetime);
1148
1149 this->defer([call]() mutable { call.perform(); });
1150 request->send(200);
1151 return;
1152 }
1153 request->send(404);
1154}
1155std::string WebServer::datetime_state_json_generator(WebServer *web_server, void *source) {
1156 return web_server->datetime_json_((datetime::DateTimeEntity *) (source), DETAIL_STATE);
1157}
1158std::string WebServer::datetime_all_json_generator(WebServer *web_server, void *source) {
1159 return web_server->datetime_json_((datetime::DateTimeEntity *) (source), DETAIL_ALL);
1160}
1161std::string WebServer::datetime_json_(datetime::DateTimeEntity *obj, JsonDetail start_config) {
1162 json::JsonBuilder builder;
1163 JsonObject root = builder.root();
1164
1165 // Format: YYYY-MM-DD HH:MM:SS (max 19 chars + null)
1166 char value[24];
1167#ifdef USE_ESP8266
1168 snprintf_P(value, sizeof(value), PSTR("%d-%02d-%02d %02d:%02d:%02d"), obj->year, obj->month, obj->day, obj->hour,
1169 obj->minute, obj->second);
1170#else
1171 snprintf(value, sizeof(value), "%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute,
1172 obj->second);
1173#endif
1174 set_json_icon_state_value(root, obj, "datetime", value, value, start_config);
1175 if (start_config == DETAIL_ALL) {
1176 this->add_sorting_info_(root, obj);
1177 }
1178
1179 return builder.serialize();
1180}
1181#endif // USE_DATETIME_DATETIME
1182
1183#ifdef USE_TEXT
1185 if (!this->include_internal_ && obj->is_internal())
1186 return;
1187 this->events_.deferrable_send_state(obj, "state", text_state_json_generator);
1188}
1189void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1190 for (auto *obj : App.get_texts()) {
1191 if (!match.id_equals_entity(obj))
1192 continue;
1193
1194 if (request->method() == HTTP_GET && match.method_empty()) {
1195 auto detail = get_request_detail(request);
1196 std::string data = this->text_json_(obj, obj->state, detail);
1197 request->send(200, "application/json", data.c_str());
1198 return;
1199 }
1200 if (!match.method_equals("set")) {
1201 request->send(404);
1202 return;
1203 }
1204
1205 auto call = obj->make_call();
1206 parse_string_param_(request, "value", call, &decltype(call)::set_value);
1207
1208 this->defer([call]() mutable { call.perform(); });
1209 request->send(200);
1210 return;
1211 }
1212 request->send(404);
1213}
1214
1215std::string WebServer::text_state_json_generator(WebServer *web_server, void *source) {
1216 return web_server->text_json_((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_STATE);
1217}
1218std::string WebServer::text_all_json_generator(WebServer *web_server, void *source) {
1219 return web_server->text_json_((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_ALL);
1220}
1221std::string WebServer::text_json_(text::Text *obj, const std::string &value, JsonDetail start_config) {
1222 json::JsonBuilder builder;
1223 JsonObject root = builder.root();
1224
1225 const char *state = obj->traits.get_mode() == text::TextMode::TEXT_MODE_PASSWORD ? "********" : value.c_str();
1226 set_json_icon_state_value(root, obj, "text", state, value.c_str(), start_config);
1227 root[ESPHOME_F("min_length")] = obj->traits.get_min_length();
1228 root[ESPHOME_F("max_length")] = obj->traits.get_max_length();
1229 root[ESPHOME_F("pattern")] = obj->traits.get_pattern_c_str();
1230 if (start_config == DETAIL_ALL) {
1231 root[ESPHOME_F("mode")] = (int) obj->traits.get_mode();
1232 this->add_sorting_info_(root, obj);
1233 }
1234
1235 return builder.serialize();
1236}
1237#endif
1238
1239#ifdef USE_SELECT
1241 if (!this->include_internal_ && obj->is_internal())
1242 return;
1243 this->events_.deferrable_send_state(obj, "state", select_state_json_generator);
1244}
1245void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1246 for (auto *obj : App.get_selects()) {
1247 if (!match.id_equals_entity(obj))
1248 continue;
1249
1250 if (request->method() == HTTP_GET && match.method_empty()) {
1251 auto detail = get_request_detail(request);
1252 std::string data = this->select_json_(obj, obj->has_state() ? obj->current_option() : "", detail);
1253 request->send(200, "application/json", data.c_str());
1254 return;
1255 }
1256
1257 if (!match.method_equals("set")) {
1258 request->send(404);
1259 return;
1260 }
1261
1262 auto call = obj->make_call();
1263 parse_string_param_(request, "option", call, &decltype(call)::set_option);
1264
1265 this->defer([call]() mutable { call.perform(); });
1266 request->send(200);
1267 return;
1268 }
1269 request->send(404);
1270}
1271std::string WebServer::select_state_json_generator(WebServer *web_server, void *source) {
1272 auto *obj = (select::Select *) (source);
1273 return web_server->select_json_(obj, obj->has_state() ? obj->current_option() : "", DETAIL_STATE);
1274}
1275std::string WebServer::select_all_json_generator(WebServer *web_server, void *source) {
1276 auto *obj = (select::Select *) (source);
1277 return web_server->select_json_(obj, obj->has_state() ? obj->current_option() : "", DETAIL_ALL);
1278}
1279std::string WebServer::select_json_(select::Select *obj, const char *value, JsonDetail start_config) {
1280 json::JsonBuilder builder;
1281 JsonObject root = builder.root();
1282
1283 set_json_icon_state_value(root, obj, "select", value, value, start_config);
1284 if (start_config == DETAIL_ALL) {
1285 JsonArray opt = root[ESPHOME_F("option")].to<JsonArray>();
1286 for (auto &option : obj->traits.get_options()) {
1287 opt.add(option);
1288 }
1289 this->add_sorting_info_(root, obj);
1290 }
1291
1292 return builder.serialize();
1293}
1294#endif
1295
1296#ifdef USE_CLIMATE
1298 if (!this->include_internal_ && obj->is_internal())
1299 return;
1300 this->events_.deferrable_send_state(obj, "state", climate_state_json_generator);
1301}
1302void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1303 for (auto *obj : App.get_climates()) {
1304 if (!match.id_equals_entity(obj))
1305 continue;
1306
1307 if (request->method() == HTTP_GET && match.method_empty()) {
1308 auto detail = get_request_detail(request);
1309 std::string data = this->climate_json_(obj, detail);
1310 request->send(200, "application/json", data.c_str());
1311 return;
1312 }
1313
1314 if (!match.method_equals("set")) {
1315 request->send(404);
1316 return;
1317 }
1318
1319 auto call = obj->make_call();
1320
1321 // Parse string mode parameters
1322 parse_string_param_(request, "mode", call, &decltype(call)::set_mode);
1323 parse_string_param_(request, "fan_mode", call, &decltype(call)::set_fan_mode);
1324 parse_string_param_(request, "swing_mode", call, &decltype(call)::set_swing_mode);
1325
1326 // Parse temperature parameters
1327 parse_float_param_(request, "target_temperature_high", call, &decltype(call)::set_target_temperature_high);
1328 parse_float_param_(request, "target_temperature_low", call, &decltype(call)::set_target_temperature_low);
1329 parse_float_param_(request, "target_temperature", call, &decltype(call)::set_target_temperature);
1330
1331 this->defer([call]() mutable { call.perform(); });
1332 request->send(200);
1333 return;
1334 }
1335 request->send(404);
1336}
1337std::string WebServer::climate_state_json_generator(WebServer *web_server, void *source) {
1338 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
1339 return web_server->climate_json_((climate::Climate *) (source), DETAIL_STATE);
1340}
1341std::string WebServer::climate_all_json_generator(WebServer *web_server, void *source) {
1342 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
1343 return web_server->climate_json_((climate::Climate *) (source), DETAIL_ALL);
1344}
1345std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_config) {
1346 // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
1347 json::JsonBuilder builder;
1348 JsonObject root = builder.root();
1349 set_json_id(root, obj, "climate", start_config);
1350 const auto traits = obj->get_traits();
1351 int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
1352 int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
1353 char buf[PSTR_LOCAL_SIZE];
1354 char temp_buf[VALUE_ACCURACY_MAX_LEN];
1355
1356 if (start_config == DETAIL_ALL) {
1357 JsonArray opt = root[ESPHOME_F("modes")].to<JsonArray>();
1358 for (climate::ClimateMode m : traits.get_supported_modes())
1359 opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m)));
1360 if (!traits.get_supported_custom_fan_modes().empty()) {
1361 JsonArray opt = root[ESPHOME_F("fan_modes")].to<JsonArray>();
1362 for (climate::ClimateFanMode m : traits.get_supported_fan_modes())
1363 opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m)));
1364 }
1365
1366 if (!traits.get_supported_custom_fan_modes().empty()) {
1367 JsonArray opt = root[ESPHOME_F("custom_fan_modes")].to<JsonArray>();
1368 for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
1369 opt.add(custom_fan_mode);
1370 }
1371 if (traits.get_supports_swing_modes()) {
1372 JsonArray opt = root[ESPHOME_F("swing_modes")].to<JsonArray>();
1373 for (auto swing_mode : traits.get_supported_swing_modes())
1375 }
1376 if (traits.get_supports_presets() && obj->preset.has_value()) {
1377 JsonArray opt = root[ESPHOME_F("presets")].to<JsonArray>();
1378 for (climate::ClimatePreset m : traits.get_supported_presets())
1379 opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m)));
1380 }
1381 if (!traits.get_supported_custom_presets().empty() && obj->has_custom_preset()) {
1382 JsonArray opt = root[ESPHOME_F("custom_presets")].to<JsonArray>();
1383 for (auto const &custom_preset : traits.get_supported_custom_presets())
1384 opt.add(custom_preset);
1385 }
1386 this->add_sorting_info_(root, obj);
1387 }
1388
1389 bool has_state = false;
1390 root[ESPHOME_F("mode")] = PSTR_LOCAL(climate_mode_to_string(obj->mode));
1391 root[ESPHOME_F("max_temp")] =
1392 (value_accuracy_to_buf(temp_buf, traits.get_visual_max_temperature(), target_accuracy), temp_buf);
1393 root[ESPHOME_F("min_temp")] =
1394 (value_accuracy_to_buf(temp_buf, traits.get_visual_min_temperature(), target_accuracy), temp_buf);
1395 root[ESPHOME_F("step")] = traits.get_visual_target_temperature_step();
1396 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
1397 root[ESPHOME_F("action")] = PSTR_LOCAL(climate_action_to_string(obj->action));
1398 root[ESPHOME_F("state")] = root[ESPHOME_F("action")];
1399 has_state = true;
1400 }
1401 if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) {
1402 root[ESPHOME_F("fan_mode")] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value()));
1403 }
1404 if (!traits.get_supported_custom_fan_modes().empty() && obj->has_custom_fan_mode()) {
1405 root[ESPHOME_F("custom_fan_mode")] = obj->get_custom_fan_mode();
1406 }
1407 if (traits.get_supports_presets() && obj->preset.has_value()) {
1408 root[ESPHOME_F("preset")] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value()));
1409 }
1410 if (!traits.get_supported_custom_presets().empty() && obj->has_custom_preset()) {
1411 root[ESPHOME_F("custom_preset")] = obj->get_custom_preset();
1412 }
1413 if (traits.get_supports_swing_modes()) {
1414 root[ESPHOME_F("swing_mode")] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode));
1415 }
1416 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
1417 root[ESPHOME_F("current_temperature")] =
1418 std::isnan(obj->current_temperature)
1419 ? "NA"
1420 : (value_accuracy_to_buf(temp_buf, obj->current_temperature, current_accuracy), temp_buf);
1421 }
1422 if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
1424 root[ESPHOME_F("target_temperature_low")] =
1425 (value_accuracy_to_buf(temp_buf, obj->target_temperature_low, target_accuracy), temp_buf);
1426 root[ESPHOME_F("target_temperature_high")] =
1427 (value_accuracy_to_buf(temp_buf, obj->target_temperature_high, target_accuracy), temp_buf);
1428 if (!has_state) {
1429 root[ESPHOME_F("state")] =
1431 target_accuracy),
1432 temp_buf);
1433 }
1434 } else {
1435 root[ESPHOME_F("target_temperature")] =
1436 (value_accuracy_to_buf(temp_buf, obj->target_temperature, target_accuracy), temp_buf);
1437 if (!has_state)
1438 root[ESPHOME_F("state")] = root[ESPHOME_F("target_temperature")];
1439 }
1440
1441 return builder.serialize();
1442 // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
1443}
1444#endif
1445
1446#ifdef USE_LOCK
1448 if (!this->include_internal_ && obj->is_internal())
1449 return;
1450 this->events_.deferrable_send_state(obj, "state", lock_state_json_generator);
1451}
1452void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1453 for (lock::Lock *obj : App.get_locks()) {
1454 if (!match.id_equals_entity(obj))
1455 continue;
1456
1457 if (request->method() == HTTP_GET && match.method_empty()) {
1458 auto detail = get_request_detail(request);
1459 std::string data = this->lock_json_(obj, obj->state, detail);
1460 request->send(200, "application/json", data.c_str());
1461 return;
1462 }
1463
1464 // Handle action methods with single defer and response
1465 enum LockAction { NONE, LOCK, UNLOCK, OPEN };
1466 LockAction action = NONE;
1467
1468 if (match.method_equals("lock")) {
1469 action = LOCK;
1470 } else if (match.method_equals("unlock")) {
1471 action = UNLOCK;
1472 } else if (match.method_equals("open")) {
1473 action = OPEN;
1474 }
1475
1476 if (action != NONE) {
1477 this->defer([obj, action]() {
1478 switch (action) {
1479 case LOCK:
1480 obj->lock();
1481 break;
1482 case UNLOCK:
1483 obj->unlock();
1484 break;
1485 case OPEN:
1486 obj->open();
1487 break;
1488 default:
1489 break;
1490 }
1491 });
1492 request->send(200);
1493 } else {
1494 request->send(404);
1495 }
1496 return;
1497 }
1498 request->send(404);
1499}
1500std::string WebServer::lock_state_json_generator(WebServer *web_server, void *source) {
1501 return web_server->lock_json_((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_STATE);
1502}
1503std::string WebServer::lock_all_json_generator(WebServer *web_server, void *source) {
1504 return web_server->lock_json_((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_ALL);
1505}
1506std::string WebServer::lock_json_(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
1507 json::JsonBuilder builder;
1508 JsonObject root = builder.root();
1509
1510 char buf[PSTR_LOCAL_SIZE];
1511 set_json_icon_state_value(root, obj, "lock", PSTR_LOCAL(lock::lock_state_to_string(value)), value, start_config);
1512 if (start_config == DETAIL_ALL) {
1513 this->add_sorting_info_(root, obj);
1514 }
1515
1516 return builder.serialize();
1517}
1518#endif
1519
1520#ifdef USE_VALVE
1522 if (!this->include_internal_ && obj->is_internal())
1523 return;
1524 this->events_.deferrable_send_state(obj, "state", valve_state_json_generator);
1525}
1526void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1527 for (valve::Valve *obj : App.get_valves()) {
1528 if (!match.id_equals_entity(obj))
1529 continue;
1530
1531 if (request->method() == HTTP_GET && match.method_empty()) {
1532 auto detail = get_request_detail(request);
1533 std::string data = this->valve_json_(obj, detail);
1534 request->send(200, "application/json", data.c_str());
1535 return;
1536 }
1537
1538 auto call = obj->make_call();
1539
1540 // Lookup table for valve methods
1541 static const struct {
1542 const char *name;
1543 valve::ValveCall &(valve::ValveCall::*action)();
1544 } METHODS[] = {
1549 };
1550
1551 bool found = false;
1552 for (const auto &method : METHODS) {
1553 if (match.method_equals(method.name)) {
1554 (call.*method.action)();
1555 found = true;
1556 break;
1557 }
1558 }
1559
1560 if (!found && !match.method_equals("set")) {
1561 request->send(404);
1562 return;
1563 }
1564
1565 auto traits = obj->get_traits();
1566 if (request->hasParam("position") && !traits.get_supports_position()) {
1567 request->send(409);
1568 return;
1569 }
1570
1571 parse_float_param_(request, "position", call, &decltype(call)::set_position);
1572
1573 this->defer([call]() mutable { call.perform(); });
1574 request->send(200);
1575 return;
1576 }
1577 request->send(404);
1578}
1579std::string WebServer::valve_state_json_generator(WebServer *web_server, void *source) {
1580 return web_server->valve_json_((valve::Valve *) (source), DETAIL_STATE);
1581}
1582std::string WebServer::valve_all_json_generator(WebServer *web_server, void *source) {
1583 return web_server->valve_json_((valve::Valve *) (source), DETAIL_ALL);
1584}
1585std::string WebServer::valve_json_(valve::Valve *obj, JsonDetail start_config) {
1586 json::JsonBuilder builder;
1587 JsonObject root = builder.root();
1588
1589 set_json_icon_state_value(root, obj, "valve", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position,
1590 start_config);
1591 char buf[PSTR_LOCAL_SIZE];
1592 root[ESPHOME_F("current_operation")] = PSTR_LOCAL(valve::valve_operation_to_str(obj->current_operation));
1593
1594 if (obj->get_traits().get_supports_position())
1595 root[ESPHOME_F("position")] = obj->position;
1596 if (start_config == DETAIL_ALL) {
1597 this->add_sorting_info_(root, obj);
1598 }
1599
1600 return builder.serialize();
1601}
1602#endif
1603
1604#ifdef USE_ALARM_CONTROL_PANEL
1606 if (!this->include_internal_ && obj->is_internal())
1607 return;
1608 this->events_.deferrable_send_state(obj, "state", alarm_control_panel_state_json_generator);
1609}
1610void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1611 for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) {
1612 if (!match.id_equals_entity(obj))
1613 continue;
1614
1615 if (request->method() == HTTP_GET && match.method_empty()) {
1616 auto detail = get_request_detail(request);
1617 std::string data = this->alarm_control_panel_json_(obj, obj->get_state(), detail);
1618 request->send(200, "application/json", data.c_str());
1619 return;
1620 }
1621
1622 auto call = obj->make_call();
1623 parse_string_param_(request, "code", call, &decltype(call)::set_code);
1624
1625 // Lookup table for alarm control panel methods
1626 static const struct {
1627 const char *name;
1629 } METHODS[] = {
1635 };
1636
1637 bool found = false;
1638 for (const auto &method : METHODS) {
1639 if (match.method_equals(method.name)) {
1640 (call.*method.action)();
1641 found = true;
1642 break;
1643 }
1644 }
1645
1646 if (!found) {
1647 request->send(404);
1648 return;
1649 }
1650
1651 this->defer([call]() mutable { call.perform(); });
1652 request->send(200);
1653 return;
1654 }
1655 request->send(404);
1656}
1658 return web_server->alarm_control_panel_json_((alarm_control_panel::AlarmControlPanel *) (source),
1659 ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(),
1660 DETAIL_STATE);
1661}
1662std::string WebServer::alarm_control_panel_all_json_generator(WebServer *web_server, void *source) {
1663 return web_server->alarm_control_panel_json_((alarm_control_panel::AlarmControlPanel *) (source),
1664 ((alarm_control_panel::AlarmControlPanel *) (source))->get_state(),
1665 DETAIL_ALL);
1666}
1667std::string WebServer::alarm_control_panel_json_(alarm_control_panel::AlarmControlPanel *obj,
1669 JsonDetail start_config) {
1670 json::JsonBuilder builder;
1671 JsonObject root = builder.root();
1672
1673 char buf[PSTR_LOCAL_SIZE];
1674 set_json_icon_state_value(root, obj, "alarm-control-panel", PSTR_LOCAL(alarm_control_panel_state_to_string(value)),
1675 value, start_config);
1676 if (start_config == DETAIL_ALL) {
1677 this->add_sorting_info_(root, obj);
1678 }
1679
1680 return builder.serialize();
1681}
1682#endif
1683
1684#ifdef USE_EVENT
1686 if (!this->include_internal_ && obj->is_internal())
1687 return;
1688 this->events_.deferrable_send_state(obj, "state", event_state_json_generator);
1689}
1690
1691void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1692 for (event::Event *obj : App.get_events()) {
1693 if (!match.id_equals_entity(obj))
1694 continue;
1695
1696 // Note: request->method() is always HTTP_GET here (canHandle ensures this)
1697 if (match.method_empty()) {
1698 auto detail = get_request_detail(request);
1699 std::string data = this->event_json_(obj, "", detail);
1700 request->send(200, "application/json", data.c_str());
1701 return;
1702 }
1703 }
1704 request->send(404);
1705}
1706
1707static std::string get_event_type(event::Event *event) {
1708 const char *last_type = event ? event->get_last_event_type() : nullptr;
1709 return last_type ? last_type : "";
1710}
1711
1712std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) {
1713 auto *event = static_cast<event::Event *>(source);
1714 return web_server->event_json_(event, get_event_type(event), DETAIL_STATE);
1715}
1716// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
1717std::string WebServer::event_all_json_generator(WebServer *web_server, void *source) {
1718 auto *event = static_cast<event::Event *>(source);
1719 return web_server->event_json_(event, get_event_type(event), DETAIL_ALL);
1720}
1721std::string WebServer::event_json_(event::Event *obj, const std::string &event_type, JsonDetail start_config) {
1722 json::JsonBuilder builder;
1723 JsonObject root = builder.root();
1724
1725 set_json_id(root, obj, "event", start_config);
1726 if (!event_type.empty()) {
1727 root[ESPHOME_F("event_type")] = event_type;
1728 }
1729 if (start_config == DETAIL_ALL) {
1730 JsonArray event_types = root[ESPHOME_F("event_types")].to<JsonArray>();
1731 for (const char *event_type : obj->get_event_types()) {
1732 event_types.add(event_type);
1733 }
1734 root[ESPHOME_F("device_class")] = obj->get_device_class_ref();
1735 this->add_sorting_info_(root, obj);
1736 }
1737
1738 return builder.serialize();
1739}
1740// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
1741#endif
1742
1743#ifdef USE_UPDATE
1744static const LogString *update_state_to_string(update::UpdateState state) {
1745 switch (state) {
1747 return LOG_STR("NO UPDATE");
1749 return LOG_STR("UPDATE AVAILABLE");
1751 return LOG_STR("INSTALLING");
1752 default:
1753 return LOG_STR("UNKNOWN");
1754 }
1755}
1756
1758 this->events_.deferrable_send_state(obj, "state", update_state_json_generator);
1759}
1760void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1761 for (update::UpdateEntity *obj : App.get_updates()) {
1762 if (!match.id_equals_entity(obj))
1763 continue;
1764
1765 if (request->method() == HTTP_GET && match.method_empty()) {
1766 auto detail = get_request_detail(request);
1767 std::string data = this->update_json_(obj, detail);
1768 request->send(200, "application/json", data.c_str());
1769 return;
1770 }
1771
1772 if (!match.method_equals("install")) {
1773 request->send(404);
1774 return;
1775 }
1776
1777 this->defer([obj]() mutable { obj->perform(); });
1778 request->send(200);
1779 return;
1780 }
1781 request->send(404);
1782}
1783std::string WebServer::update_state_json_generator(WebServer *web_server, void *source) {
1784 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
1785 return web_server->update_json_((update::UpdateEntity *) (source), DETAIL_STATE);
1786}
1787std::string WebServer::update_all_json_generator(WebServer *web_server, void *source) {
1788 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
1789 return web_server->update_json_((update::UpdateEntity *) (source), DETAIL_STATE);
1790}
1791std::string WebServer::update_json_(update::UpdateEntity *obj, JsonDetail start_config) {
1792 // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
1793 json::JsonBuilder builder;
1794 JsonObject root = builder.root();
1795
1796 char buf[PSTR_LOCAL_SIZE];
1797 set_json_icon_state_value(root, obj, "update", PSTR_LOCAL(update_state_to_string(obj->state)),
1798 obj->update_info.latest_version, start_config);
1799 if (start_config == DETAIL_ALL) {
1800 root[ESPHOME_F("current_version")] = obj->update_info.current_version;
1801 root[ESPHOME_F("title")] = obj->update_info.title;
1802 root[ESPHOME_F("summary")] = obj->update_info.summary;
1803 root[ESPHOME_F("release_url")] = obj->update_info.release_url;
1804 this->add_sorting_info_(root, obj);
1805 }
1806
1807 return builder.serialize();
1808 // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
1809}
1810#endif
1811
1812bool WebServer::canHandle(AsyncWebServerRequest *request) const {
1813 const auto &url = request->url();
1814 const auto method = request->method();
1815
1816 // Static URL checks
1817 static const char *const STATIC_URLS[] = {
1818 "/",
1819#if !defined(USE_ESP32) && defined(USE_ARDUINO)
1820 "/events",
1821#endif
1822#ifdef USE_WEBSERVER_CSS_INCLUDE
1823 "/0.css",
1824#endif
1825#ifdef USE_WEBSERVER_JS_INCLUDE
1826 "/0.js",
1827#endif
1828 };
1829
1830 for (const auto &static_url : STATIC_URLS) {
1831 if (url == static_url)
1832 return true;
1833 }
1834
1835#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
1836 if (method == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network")))
1837 return true;
1838#endif
1839
1840 // Parse URL for component checks
1841 UrlMatch match = match_url(url.c_str(), url.length(), true);
1842 if (!match.valid)
1843 return false;
1844
1845 // Common pattern check
1846 bool is_get = method == HTTP_GET;
1847 bool is_post = method == HTTP_POST;
1848 bool is_get_or_post = is_get || is_post;
1849
1850 if (!is_get_or_post)
1851 return false;
1852
1853 // Use lookup tables for domain checks
1854 static const char *const GET_ONLY_DOMAINS[] = {
1855#ifdef USE_SENSOR
1856 "sensor",
1857#endif
1858#ifdef USE_BINARY_SENSOR
1859 "binary_sensor",
1860#endif
1861#ifdef USE_TEXT_SENSOR
1862 "text_sensor",
1863#endif
1864#ifdef USE_EVENT
1865 "event",
1866#endif
1867 };
1868
1869 static const char *const GET_POST_DOMAINS[] = {
1870#ifdef USE_SWITCH
1871 "switch",
1872#endif
1873#ifdef USE_BUTTON
1874 "button",
1875#endif
1876#ifdef USE_FAN
1877 "fan",
1878#endif
1879#ifdef USE_LIGHT
1880 "light",
1881#endif
1882#ifdef USE_COVER
1883 "cover",
1884#endif
1885#ifdef USE_NUMBER
1886 "number",
1887#endif
1888#ifdef USE_DATETIME_DATE
1889 "date",
1890#endif
1891#ifdef USE_DATETIME_TIME
1892 "time",
1893#endif
1894#ifdef USE_DATETIME_DATETIME
1895 "datetime",
1896#endif
1897#ifdef USE_TEXT
1898 "text",
1899#endif
1900#ifdef USE_SELECT
1901 "select",
1902#endif
1903#ifdef USE_CLIMATE
1904 "climate",
1905#endif
1906#ifdef USE_LOCK
1907 "lock",
1908#endif
1909#ifdef USE_VALVE
1910 "valve",
1911#endif
1912#ifdef USE_ALARM_CONTROL_PANEL
1913 "alarm_control_panel",
1914#endif
1915#ifdef USE_UPDATE
1916 "update",
1917#endif
1918 };
1919
1920 // Check GET-only domains
1921 if (is_get) {
1922 for (const auto &domain : GET_ONLY_DOMAINS) {
1923 if (match.domain_equals(domain))
1924 return true;
1925 }
1926 }
1927
1928 // Check GET+POST domains
1929 if (is_get_or_post) {
1930 for (const auto &domain : GET_POST_DOMAINS) {
1931 if (match.domain_equals(domain))
1932 return true;
1933 }
1934 }
1935
1936 return false;
1937}
1938void WebServer::handleRequest(AsyncWebServerRequest *request) {
1939 const auto &url = request->url();
1940
1941 // Handle static routes first
1942 if (url == "/") {
1943 this->handle_index_request(request);
1944 return;
1945 }
1946
1947#if !defined(USE_ESP32) && defined(USE_ARDUINO)
1948 if (url == "/events") {
1949 this->events_.add_new_client(this, request);
1950 return;
1951 }
1952#endif
1953
1954#ifdef USE_WEBSERVER_CSS_INCLUDE
1955 if (url == "/0.css") {
1956 this->handle_css_request(request);
1957 return;
1958 }
1959#endif
1960
1961#ifdef USE_WEBSERVER_JS_INCLUDE
1962 if (url == "/0.js") {
1963 this->handle_js_request(request);
1964 return;
1965 }
1966#endif
1967
1968#ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
1969 if (request->method() == HTTP_OPTIONS && request->hasHeader(ESPHOME_F("Access-Control-Request-Private-Network"))) {
1970 this->handle_pna_cors_request(request);
1971 return;
1972 }
1973#endif
1974
1975 // Parse URL for component routing
1976 UrlMatch match = match_url(url.c_str(), url.length(), false);
1977
1978 // Route to appropriate handler based on domain
1979 // NOLINTNEXTLINE(readability-simplify-boolean-expr)
1980 if (false) { // Start chain for else-if macro pattern
1981 }
1982#ifdef USE_SENSOR
1983 else if (match.domain_equals("sensor")) {
1984 this->handle_sensor_request(request, match);
1985 }
1986#endif
1987#ifdef USE_SWITCH
1988 else if (match.domain_equals("switch")) {
1989 this->handle_switch_request(request, match);
1990 }
1991#endif
1992#ifdef USE_BUTTON
1993 else if (match.domain_equals("button")) {
1994 this->handle_button_request(request, match);
1995 }
1996#endif
1997#ifdef USE_BINARY_SENSOR
1998 else if (match.domain_equals("binary_sensor")) {
1999 this->handle_binary_sensor_request(request, match);
2000 }
2001#endif
2002#ifdef USE_FAN
2003 else if (match.domain_equals("fan")) {
2004 this->handle_fan_request(request, match);
2005 }
2006#endif
2007#ifdef USE_LIGHT
2008 else if (match.domain_equals("light")) {
2009 this->handle_light_request(request, match);
2010 }
2011#endif
2012#ifdef USE_TEXT_SENSOR
2013 else if (match.domain_equals("text_sensor")) {
2014 this->handle_text_sensor_request(request, match);
2015 }
2016#endif
2017#ifdef USE_COVER
2018 else if (match.domain_equals("cover")) {
2019 this->handle_cover_request(request, match);
2020 }
2021#endif
2022#ifdef USE_NUMBER
2023 else if (match.domain_equals("number")) {
2024 this->handle_number_request(request, match);
2025 }
2026#endif
2027#ifdef USE_DATETIME_DATE
2028 else if (match.domain_equals("date")) {
2029 this->handle_date_request(request, match);
2030 }
2031#endif
2032#ifdef USE_DATETIME_TIME
2033 else if (match.domain_equals("time")) {
2034 this->handle_time_request(request, match);
2035 }
2036#endif
2037#ifdef USE_DATETIME_DATETIME
2038 else if (match.domain_equals("datetime")) {
2039 this->handle_datetime_request(request, match);
2040 }
2041#endif
2042#ifdef USE_TEXT
2043 else if (match.domain_equals("text")) {
2044 this->handle_text_request(request, match);
2045 }
2046#endif
2047#ifdef USE_SELECT
2048 else if (match.domain_equals("select")) {
2049 this->handle_select_request(request, match);
2050 }
2051#endif
2052#ifdef USE_CLIMATE
2053 else if (match.domain_equals("climate")) {
2054 this->handle_climate_request(request, match);
2055 }
2056#endif
2057#ifdef USE_LOCK
2058 else if (match.domain_equals("lock")) {
2059 this->handle_lock_request(request, match);
2060 }
2061#endif
2062#ifdef USE_VALVE
2063 else if (match.domain_equals("valve")) {
2064 this->handle_valve_request(request, match);
2065 }
2066#endif
2067#ifdef USE_ALARM_CONTROL_PANEL
2068 else if (match.domain_equals("alarm_control_panel")) {
2069 this->handle_alarm_control_panel_request(request, match);
2070 }
2071#endif
2072#ifdef USE_UPDATE
2073 else if (match.domain_equals("update")) {
2074 this->handle_update_request(request, match);
2075 }
2076#endif
2077 else {
2078 // No matching handler found - send 404
2079 ESP_LOGV(TAG, "Request for unknown URL: %s", url.c_str());
2080 request->send(404, "text/plain", "Not Found");
2081 }
2082}
2083
2084bool WebServer::isRequestHandlerTrivial() const { return false; }
2085
2086void WebServer::add_sorting_info_(JsonObject &root, EntityBase *entity) {
2087#ifdef USE_WEBSERVER_SORTING
2088 if (this->sorting_entitys_.find(entity) != this->sorting_entitys_.end()) {
2089 root[ESPHOME_F("sorting_weight")] = this->sorting_entitys_[entity].weight;
2090 if (this->sorting_groups_.find(this->sorting_entitys_[entity].group_id) != this->sorting_groups_.end()) {
2091 root[ESPHOME_F("sorting_group")] = this->sorting_groups_[this->sorting_entitys_[entity].group_id].name;
2092 }
2093 }
2094#endif
2095}
2096
2097#ifdef USE_WEBSERVER_SORTING
2098void WebServer::add_entity_config(EntityBase *entity, float weight, uint64_t group) {
2099 this->sorting_entitys_[entity] = SortingComponents{weight, group};
2100}
2101
2102void WebServer::add_sorting_group(uint64_t group_id, const std::string &group_name, float weight) {
2103 this->sorting_groups_[group_id] = SortingGroup{group_name, weight};
2104}
2105#endif
2106
2107} // namespace esphome::web_server
2108#endif
uint8_t m
Definition bl0906.h:1
const std::string & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
const std::string & get_name() const
Get the name of this Application set by pre_setup().
void get_comment_string(std::span< char, ESPHOME_COMMENT_SIZE > buffer)
Copy the comment string into the provided buffer Buffer must be ESPHOME_COMMENT_SIZE bytes (compile-t...
auto & get_binary_sensors() const
void set_interval(const std::string &name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a unique name.
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
void begin(bool include_internal=false)
static void register_controller(Controller *controller)
Register a controller to receive entity state updates.
StringRef get_device_class_ref() const
Get the device class as StringRef.
StringRef get_unit_of_measurement_ref() const
Get the unit of measurement as StringRef.
bool is_internal() const
Definition entity_base.h:64
const StringRef & get_name() const
StringRef get_icon_ref() const
Definition entity_base.h:85
size_t write_object_id_to(char *buf, size_t buf_size) const
Write object_id directly to buffer, returns length written (excluding null) Useful for building compo...
bool is_disabled_by_default() const
Definition entity_base.h:70
bool has_state() const
EntityCategory get_entity_category() const
Definition entity_base.h:74
Base class for all binary_sensor-type classes.
Base class for all buttons.
Definition button.h:25
ClimateDevice - This is the base class for all climate integrations.
Definition climate.h:181
ClimateMode mode
The active mode of the climate device.
Definition climate.h:261
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition climate.h:255
ClimateTraits get_traits()
Get the traits of this climate device with all overrides applied.
Definition climate.cpp:487
float target_temperature
The target temperature of the climate device.
Definition climate.h:242
const char * get_custom_fan_mode() const
Get the active custom fan mode (read-only access).
Definition climate.h:270
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition climate.h:267
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:245
const char * get_custom_preset() const
Get the active custom preset (read-only access).
Definition climate.h:273
bool has_custom_preset() const
Check if a custom preset is currently active.
Definition climate.h:232
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition climate.h:235
ClimateAction action
The active state of the climate device.
Definition climate.h:264
bool has_custom_fan_mode() const
Check if a custom fan mode is currently active.
Definition climate.h:229
optional< ClimatePreset > preset
The active preset of the climate device.
Definition climate.h:258
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition climate.h:247
int8_t get_target_temperature_accuracy_decimals() const
CoverCall & set_command_toggle()
Set the command to toggle the cover.
Definition cover.cpp:67
CoverCall & set_command_open()
Set the command to open the cover.
Definition cover.cpp:55
CoverCall & set_command_close()
Set the command to close the cover.
Definition cover.cpp:59
CoverCall & set_command_stop()
Set the command to stop the cover.
Definition cover.cpp:63
Base class for all cover devices.
Definition cover.h:112
CoverOperation current_operation
The current operation of the cover (idle, opening, closing).
Definition cover.h:117
CoverCall make_call()
Construct a new cover call used to control the cover.
Definition cover.cpp:149
float tilt
The current tilt value of the cover from 0.0 to 1.0.
Definition cover.h:125
float position
The position of the cover from 0.0 (fully closed) to 1.0 (fully open).
Definition cover.h:123
bool is_fully_closed() const
Helper method to check if the cover is fully closed. Equivalent to comparing .position against 0....
Definition cover.cpp:198
virtual CoverTraits get_traits()=0
bool get_supports_position() const
const FixedVector< const char * > & get_event_types() const
Return the event types supported by this event.
Definition event.h:45
FanCall turn_on()
Definition fan.cpp:144
FanCall turn_off()
Definition fan.cpp:145
virtual FanTraits get_traits()=0
FanCall toggle()
Definition fan.cpp:146
bool oscillating
The current oscillation state of the fan.
Definition fan.h:110
bool state
The current on/off state of the fan.
Definition fan.h:108
int speed
The current fan speed level.
Definition fan.h:112
bool supports_oscillation() const
Return if this fan supports oscillation.
Definition fan_traits.h:18
Builder class for creating JSON documents without lambdas.
Definition json_util.h:62
bool is_on() const
Get the binary true/false state of these light color values.
static void dump_json(LightState &state, JsonObject root)
Dump the state of a light as JSON.
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:91
LightColorValues remote_values
The remote color values reported to the frontend.
const FixedVector< LightEffect * > & get_effects() const
Get all effects for this light state.
Base class for all locks.
Definition lock.h:111
LockCall make_call()
Make a lock device control call, this is used to control the lock device, see the LockCall descriptio...
Definition lock.cpp:29
void add_log_listener(LogListener *listener)
Register a log listener to receive log messages.
Definition logger.h:207
Base-class for all numbers.
Definition number.h:29
NumberCall make_call()
Definition number.h:35
NumberTraits traits
Definition number.h:39
NumberMode get_mode() const
bool has_value() const
Definition optional.h:92
value_type const & value() const
Definition optional.h:94
Base-class for all selects.
Definition select.h:30
SelectCall make_call()
Instantiate a SelectCall object to modify this select component's state.
Definition select.h:52
SelectTraits traits
Definition select.h:32
const FixedVector< const char * > & get_options() const
Base-class for all sensors.
Definition sensor.h:43
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:117
int8_t get_accuracy_decimals()
Get the accuracy in decimals, using the manual override if set.
Definition sensor.cpp:57
Base class for all switches.
Definition switch.h:39
virtual bool assumed_state()
Return whether this switch uses an assumed state - i.e.
Definition switch.cpp:71
Base-class for all text inputs.
Definition text.h:24
TextCall make_call()
Instantiate a TextCall object to modify this text component's state.
Definition text.h:32
TextTraits traits
Definition text.h:27
TextMode get_mode() const
Definition text_traits.h:31
const char * get_pattern_c_str() const
Definition text_traits.h:26
const UpdateState & state
const UpdateInfo & update_info
ValveCall & set_command_close()
Set the command to close the valve.
Definition valve.cpp:58
ValveCall & set_command_toggle()
Set the command to toggle the valve.
Definition valve.cpp:66
ValveCall & set_command_stop()
Set the command to stop the valve.
Definition valve.cpp:62
ValveCall & set_command_open()
Set the command to open the valve.
Definition valve.cpp:54
Base class for all valve devices.
Definition valve.h:106
bool is_fully_closed() const
Helper method to check if the valve is fully closed. Equivalent to comparing .position against 0....
Definition valve.cpp:172
float position
The position of the valve from 0.0 (fully closed) to 1.0 (fully open).
Definition valve.h:117
ValveCall make_call()
Construct a new valve call used to control the valve.
Definition valve.cpp:130
ValveOperation current_operation
The current operation of the valve (idle, opening, closing).
Definition valve.h:111
virtual ValveTraits get_traits()=0
bool get_supports_position() const
void try_send_nodefer(const char *message, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
static constexpr uint16_t MAX_CONSECUTIVE_SEND_FAILURES
Definition web_server.h:130
void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator)
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)
std::vector< DeferredEvent > deferred_queue_
Definition web_server.h:127
void on_client_connect_(DeferredUpdateEventSource *source)
void add_new_client(WebServer *ws, AsyncWebServerRequest *request)
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)
void try_send_nodefer(const char *message, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
void on_client_disconnect_(DeferredUpdateEventSource *source)
This class allows users to create a web server with their ESP nodes.
Definition web_server.h:178
void setup() override
Setup the internal web server and register handlers.
void on_update(update::UpdateEntity *obj) override
static std::string text_sensor_all_json_generator(WebServer *web_server, void *source)
std::string get_config_json()
Return the webserver configuration as JSON.
std::map< EntityBase *, SortingComponents > sorting_entitys_
Definition web_server.h:467
static std::string binary_sensor_state_json_generator(WebServer *web_server, void *source)
void on_text_update(text::Text *obj) override
static std::string button_state_json_generator(WebServer *web_server, void *source)
static std::string lock_all_json_generator(WebServer *web_server, void *source)
void on_light_update(light::LightState *obj) override
static std::string date_all_json_generator(WebServer *web_server, void *source)
void on_cover_update(cover::Cover *obj) override
static std::string text_state_json_generator(WebServer *web_server, void *source)
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a select request under '/select/<id>'.
static std::string event_state_json_generator(WebServer *web_server, void *source)
static std::string datetime_all_json_generator(WebServer *web_server, void *source)
static std::string sensor_all_json_generator(WebServer *web_server, void *source)
bool isRequestHandlerTrivial() const override
This web handle is not trivial.
WebServer(web_server_base::WebServerBase *base)
void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a switch request under '/switch/<id>/</turn_on/turn_off/toggle>'.
void handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a event request under '/event<id>'.
void parse_light_param_uint_(AsyncWebServerRequest *request, const char *param_name, T &call, Ret(T::*setter)(uint32_t), uint32_t scale=1)
Definition web_server.h:491
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override
void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a button request under '/button/<id>/press'.
void on_date_update(datetime::DateEntity *obj) override
void on_number_update(number::Number *obj) override
void add_entity_config(EntityBase *entity, float weight, uint64_t group)
void handle_css_request(AsyncWebServerRequest *request)
Handle included css request under '/0.css'.
static std::string sensor_state_json_generator(WebServer *web_server, void *source)
void on_valve_update(valve::Valve *obj) override
void on_climate_update(climate::Climate *obj) override
static std::string switch_state_json_generator(WebServer *web_server, void *source)
void add_sorting_info_(JsonObject &root, EntityBase *entity)
void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a light request under '/light/<id>/</turn_on/turn_off/toggle>'.
static std::string event_all_json_generator(WebServer *web_server, void *source)
static std::string climate_state_json_generator(WebServer *web_server, void *source)
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override
static std::string number_all_json_generator(WebServer *web_server, void *source)
void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a text input request under '/text/<id>'.
void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a cover request under '/cover/<id>/<open/close/stop/set>'.
static std::string date_state_json_generator(WebServer *web_server, void *source)
static std::string valve_all_json_generator(WebServer *web_server, void *source)
static std::string text_all_json_generator(WebServer *web_server, void *source)
void on_switch_update(switch_::Switch *obj) override
web_server_base::WebServerBase * base_
Definition web_server.h:535
static std::string binary_sensor_all_json_generator(WebServer *web_server, void *source)
static std::string light_state_json_generator(WebServer *web_server, void *source)
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a lock request under '/lock/<id>/</lock/unlock/open>'.
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override
static std::string light_all_json_generator(WebServer *web_server, void *source)
void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a text sensor request under '/text_sensor/<id>'.
void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a date request under '/date/<id>'.
static std::string cover_all_json_generator(WebServer *web_server, void *source)
void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a sensor request under '/sensor/<id>'.
static std::string text_sensor_state_json_generator(WebServer *web_server, void *source)
void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a number request under '/number/<id>'.
static std::string alarm_control_panel_state_json_generator(WebServer *web_server, void *source)
void handle_index_request(AsyncWebServerRequest *request)
Handle an index request under '/'.
void handle_js_request(AsyncWebServerRequest *request)
Handle included js request under '/0.js'.
void set_js_include(const char *js_include)
Set local path to the script that's embedded in the index page.
static std::string fan_state_json_generator(WebServer *web_server, void *source)
static std::string update_state_json_generator(WebServer *web_server, void *source)
void handleRequest(AsyncWebServerRequest *request) override
Override the web handler's handleRequest method.
static std::string climate_all_json_generator(WebServer *web_server, void *source)
void on_datetime_update(datetime::DateTimeEntity *obj) override
void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a fan request under '/fan/<id>/</turn_on/turn_off/toggle>'.
static std::string cover_state_json_generator(WebServer *web_server, void *source)
static std::string lock_state_json_generator(WebServer *web_server, void *source)
void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a valve request under '/valve/<id>/<open/close/stop/set>'.
void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a binary sensor request under '/binary_sensor/<id>'.
static std::string alarm_control_panel_all_json_generator(WebServer *web_server, void *source)
static std::string number_state_json_generator(WebServer *web_server, void *source)
void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a time request under '/time/<id>'.
void on_sensor_update(sensor::Sensor *obj) override
std::map< uint64_t, SortingGroup > sorting_groups_
Definition web_server.h:468
void set_css_include(const char *css_include)
Set local path to the script that's embedded in the index page.
static std::string valve_state_json_generator(WebServer *web_server, void *source)
bool canHandle(AsyncWebServerRequest *request) const override
Override the web handler's canHandle method.
void on_event(event::Event *obj) override
void handle_pna_cors_request(AsyncWebServerRequest *request)
void on_fan_update(fan::Fan *obj) override
void parse_light_param_(AsyncWebServerRequest *request, const char *param_name, T &call, Ret(T::*setter)(float), float scale=1.0f)
Definition web_server.h:479
void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a datetime request under '/datetime/<id>'.
void parse_string_param_(AsyncWebServerRequest *request, const char *param_name, T &call, Ret(T::*setter)(const std::string &))
Definition web_server.h:526
void parse_float_param_(AsyncWebServerRequest *request, const char *param_name, T &call, Ret(T::*setter)(float))
Definition web_server.h:504
void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a alarm_control_panel request under '/alarm_control_panel/<id>'.
static std::string time_state_json_generator(WebServer *web_server, void *source)
void on_lock_update(lock::Lock *obj) override
static std::string button_all_json_generator(WebServer *web_server, void *source)
static std::string select_state_json_generator(WebServer *web_server, void *source)
float get_setup_priority() const override
MQTT setup priority.
void parse_int_param_(AsyncWebServerRequest *request, const char *param_name, T &call, Ret(T::*setter)(int))
Definition web_server.h:515
void on_select_update(select::Select *obj) override
void on_time_update(datetime::TimeEntity *obj) override
static std::string update_all_json_generator(WebServer *web_server, void *source)
void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a update request under '/update/<id>'.
static std::string fan_all_json_generator(WebServer *web_server, void *source)
static std::string switch_all_json_generator(WebServer *web_server, void *source)
static std::string time_all_json_generator(WebServer *web_server, void *source)
void add_sorting_group(uint64_t group_id, const std::string &group_name, float weight)
static std::string select_all_json_generator(WebServer *web_server, void *source)
static std::string datetime_state_json_generator(WebServer *web_server, void *source)
void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a climate request under '/climate/<id>'.
void on_text_sensor_update(text_sensor::TextSensor *obj) override
void add_handler(AsyncWebHandler *handler)
ClimateSwingMode swing_mode
Definition climate.h:11
uint8_t custom_preset
Definition climate.h:9
uint8_t custom_fan_mode
Definition climate.h:4
const char * message
Definition component.cpp:38
int speed
Definition fan.h:1
bool state
Definition fan.h:0
mopeka_std_values val[4]
const LogString * climate_action_to_string(ClimateAction action)
Convert the given ClimateAction to a human-readable string.
@ CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE
@ CLIMATE_SUPPORTS_CURRENT_TEMPERATURE
@ CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE
const LogString * climate_swing_mode_to_string(ClimateSwingMode swing_mode)
Convert the given ClimateSwingMode to a human-readable string.
const LogString * climate_preset_to_string(ClimatePreset preset)
Convert the given PresetMode to a human-readable string.
ClimatePreset
Enum for all preset modes NOTE: If adding values, update ClimatePresetMask in climate_traits....
const LogString * climate_fan_mode_to_string(ClimateFanMode fan_mode)
Convert the given ClimateFanMode to a human-readable string.
ClimateMode
Enum for all modes a climate device can be in.
const LogString * climate_mode_to_string(ClimateMode mode)
Convert the given ClimateMode to a human-readable string.
ClimateFanMode
NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value.
const LogString * cover_operation_to_str(CoverOperation op)
Definition cover.cpp:25
const LogString * lock_state_to_string(LockState state)
Definition lock.cpp:10
LockState
Enum for all states a lock can be in.
Definition lock.h:25
Logger * global_logger
Definition logger.cpp:297
const char * get_use_address()
Get the active network hostname.
Definition util.cpp:88
const char *const TAG
Definition spi.cpp:7
const LogString * valve_operation_to_str(ValveOperation op)
Definition valve.cpp:24
std::string(WebServer *, void *) message_generator_t
Definition web_server.h:90
size_t value_accuracy_to_buf(std::span< char, VALUE_ACCURACY_MAX_LEN > buf, float value, int8_t accuracy_decimals)
Format value with accuracy to buffer, returns chars written (excluding null)
Definition helpers.cpp:421
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:392
std::string size_t len
Definition helpers.h:533
int8_t step_to_accuracy_decimals(float step)
Derive accuracy in decimals from an increment step.
Definition helpers.cpp:445
const char * get_mac_address_pretty_into_buffer(std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in colon-separated uppercase hex notation.
Definition helpers.cpp:710
size_t value_accuracy_with_uom_to_buf(std::span< char, VALUE_ACCURACY_MAX_LEN > buf, float value, int8_t accuracy_decimals, StringRef unit_of_measurement)
Format value with accuracy and UOM to buffer, returns chars written (excluding null)
Definition helpers.cpp:431
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
@ PARSE_ON
Definition helpers.h:945
@ PARSE_TOGGLE
Definition helpers.h:947
@ PARSE_OFF
Definition helpers.h:946
@ PARSE_NONE
Definition helpers.h:944
Internal helper struct that is used to parse incoming URLs.
Definition web_server.h:39
const char * domain
Pointer to domain within URL, for example "sensor".
Definition web_server.h:40
bool valid
Whether this match is valid.
Definition web_server.h:46
bool id_equals_entity(EntityBase *entity) const
Definition web_server.h:53
bool domain_equals(const char *str) const
Definition web_server.h:49
bool method_equals(const char *str) const
Definition web_server.h:60
uint8_t end[39]
Definition sun_gtil2.cpp:17
friend class DeferredUpdateEventSource
Definition web_server.h:0
const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE
const size_t ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE
const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE