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