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