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