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