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