ESPHome  2024.3.1
web_server.cpp
Go to the documentation of this file.
1 #include "web_server.h"
2 
7 #include "esphome/core/helpers.h"
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 #include "server_index.h"
31 #endif
32 
33 namespace esphome {
34 namespace web_server {
35 
36 static const char *const TAG = "web_server";
37 
38 #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
39 static const char *const HEADER_PNA_NAME = "Private-Network-Access-Name";
40 static const char *const HEADER_PNA_ID = "Private-Network-Access-ID";
41 static const char *const HEADER_CORS_REQ_PNA = "Access-Control-Request-Private-Network";
42 static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-Network";
43 #endif
44 
45 #if USE_WEBSERVER_VERSION == 1
46 void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action,
47  const std::function<void(AsyncResponseStream &stream, EntityBase *obj)> &action_func = nullptr) {
48  stream->print("<tr class=\"");
49  stream->print(klass.c_str());
50  if (obj->is_internal())
51  stream->print(" internal");
52  stream->print("\" id=\"");
53  stream->print(klass.c_str());
54  stream->print("-");
55  stream->print(obj->get_object_id().c_str());
56  stream->print("\"><td>");
57  stream->print(obj->get_name().c_str());
58  stream->print("</td><td></td><td>");
59  stream->print(action.c_str());
60  if (action_func) {
61  action_func(*stream, obj);
62  }
63  stream->print("</td>");
64  stream->print("</tr>");
65 }
66 #endif
67 
68 UrlMatch match_url(const std::string &url, bool only_domain = false) {
69  UrlMatch match;
70  match.valid = false;
71  size_t domain_end = url.find('/', 1);
72  if (domain_end == std::string::npos)
73  return match;
74  match.domain = url.substr(1, domain_end - 1);
75  if (only_domain) {
76  match.valid = true;
77  return match;
78  }
79  if (url.length() == domain_end - 1)
80  return match;
81  size_t id_begin = domain_end + 1;
82  size_t id_end = url.find('/', id_begin);
83  match.valid = true;
84  if (id_end == std::string::npos) {
85  match.id = url.substr(id_begin, url.length() - id_begin);
86  return match;
87  }
88  match.id = url.substr(id_begin, id_end - id_begin);
89  size_t method_begin = id_end + 1;
90  match.method = url.substr(method_begin, url.length() - method_begin);
91  return match;
92 }
93 
95  : base_(base), entities_iterator_(ListEntitiesIterator(this)) {
96 #ifdef USE_ESP32
97  to_schedule_lock_ = xSemaphoreCreateMutex();
98 #endif
99 }
100 
101 #if USE_WEBSERVER_VERSION == 1
102 void WebServer::set_css_url(const char *css_url) { this->css_url_ = css_url; }
103 void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; }
104 #endif
105 
106 #ifdef USE_WEBSERVER_CSS_INCLUDE
107 void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; }
108 #endif
109 #ifdef USE_WEBSERVER_JS_INCLUDE
110 void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; }
111 #endif
112 
114  return json::build_json([this](JsonObject root) {
115  root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name();
116  root["comment"] = App.get_comment();
117  root["ota"] = this->allow_ota_;
118  root["log"] = this->expose_log_;
119  root["lang"] = "en";
120  });
121 }
122 
124  ESP_LOGCONFIG(TAG, "Setting up web server...");
125  this->setup_controller(this->include_internal_);
126  this->base_->init();
127 
128  this->events_.onConnect([this](AsyncEventSourceClient *client) {
129  // Configure reconnect timeout and send config
130  client->send(this->get_config_json().c_str(), "ping", millis(), 30000);
131 
133  });
134 
135 #ifdef USE_LOGGER
136  if (logger::global_logger != nullptr && this->expose_log_) {
138  [this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); });
139  }
140 #endif
141  this->base_->add_handler(&this->events_);
142  this->base_->add_handler(this);
143 
144  if (this->allow_ota_)
145  this->base_->add_ota_handler();
146 
147  this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); });
148 }
150 #ifdef USE_ESP32
151  if (xSemaphoreTake(this->to_schedule_lock_, 0L)) {
152  std::function<void()> fn;
153  if (!to_schedule_.empty()) {
154  // scheduler execute things out of order which may lead to incorrect state
155  // this->defer(std::move(to_schedule_.front()));
156  // let's execute it directly from the loop
157  fn = std::move(to_schedule_.front());
158  to_schedule_.pop_front();
159  }
160  xSemaphoreGive(this->to_schedule_lock_);
161  if (fn) {
162  fn();
163  }
164  }
165 #endif
166  this->entities_iterator_.advance();
167 }
169  ESP_LOGCONFIG(TAG, "Web Server:");
170  ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port());
171 }
172 float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
173 
174 #ifdef USE_WEBSERVER_LOCAL
175 void WebServer::handle_index_request(AsyncWebServerRequest *request) {
176  AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
177  response->addHeader("Content-Encoding", "gzip");
178  request->send(response);
179 }
180 #elif USE_WEBSERVER_VERSION == 1
181 void WebServer::handle_index_request(AsyncWebServerRequest *request) {
182  AsyncResponseStream *stream = request->beginResponseStream("text/html");
183  const std::string &title = App.get_name();
184  stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta "
185  "name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>"));
186  stream->print(title.c_str());
187  stream->print(F("</title>"));
188 #ifdef USE_WEBSERVER_CSS_INCLUDE
189  stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">"));
190 #endif
191  if (strlen(this->css_url_) > 0) {
192  stream->print(F(R"(<link rel="stylesheet" href=")"));
193  stream->print(this->css_url_);
194  stream->print(F("\">"));
195  }
196  stream->print(F("</head><body>"));
197  stream->print(F("<article class=\"markdown-body\"><h1>"));
198  stream->print(title.c_str());
199  stream->print(F("</h1>"));
200  stream->print(F("<h2>States</h2><table id=\"states\"><thead><tr><th>Name<th>State<th>Actions<tbody>"));
201 
202 #ifdef USE_SENSOR
203  for (auto *obj : App.get_sensors()) {
204  if (this->include_internal_ || !obj->is_internal())
205  write_row(stream, obj, "sensor", "");
206  }
207 #endif
208 
209 #ifdef USE_SWITCH
210  for (auto *obj : App.get_switches()) {
211  if (this->include_internal_ || !obj->is_internal())
212  write_row(stream, obj, "switch", "<button>Toggle</button>");
213  }
214 #endif
215 
216 #ifdef USE_BUTTON
217  for (auto *obj : App.get_buttons())
218  write_row(stream, obj, "button", "<button>Press</button>");
219 #endif
220 
221 #ifdef USE_BINARY_SENSOR
222  for (auto *obj : App.get_binary_sensors()) {
223  if (this->include_internal_ || !obj->is_internal())
224  write_row(stream, obj, "binary_sensor", "");
225  }
226 #endif
227 
228 #ifdef USE_FAN
229  for (auto *obj : App.get_fans()) {
230  if (this->include_internal_ || !obj->is_internal())
231  write_row(stream, obj, "fan", "<button>Toggle</button>");
232  }
233 #endif
234 
235 #ifdef USE_LIGHT
236  for (auto *obj : App.get_lights()) {
237  if (this->include_internal_ || !obj->is_internal())
238  write_row(stream, obj, "light", "<button>Toggle</button>");
239  }
240 #endif
241 
242 #ifdef USE_TEXT_SENSOR
243  for (auto *obj : App.get_text_sensors()) {
244  if (this->include_internal_ || !obj->is_internal())
245  write_row(stream, obj, "text_sensor", "");
246  }
247 #endif
248 
249 #ifdef USE_COVER
250  for (auto *obj : App.get_covers()) {
251  if (this->include_internal_ || !obj->is_internal())
252  write_row(stream, obj, "cover", "<button>Open</button><button>Close</button>");
253  }
254 #endif
255 
256 #ifdef USE_NUMBER
257  for (auto *obj : App.get_numbers()) {
258  if (this->include_internal_ || !obj->is_internal()) {
259  write_row(stream, obj, "number", "", [](AsyncResponseStream &stream, EntityBase *obj) {
260  number::Number *number = (number::Number *) obj;
261  stream.print(R"(<input type="number" min=")");
262  stream.print(number->traits.get_min_value());
263  stream.print(R"(" max=")");
264  stream.print(number->traits.get_max_value());
265  stream.print(R"(" step=")");
266  stream.print(number->traits.get_step());
267  stream.print(R"(" value=")");
268  stream.print(number->state);
269  stream.print(R"("/>)");
270  });
271  }
272  }
273 #endif
274 
275 #ifdef USE_TEXT
276  for (auto *obj : App.get_texts()) {
277  if (this->include_internal_ || !obj->is_internal()) {
278  write_row(stream, obj, "text", "", [](AsyncResponseStream &stream, EntityBase *obj) {
279  text::Text *text = (text::Text *) obj;
280  auto mode = (int) text->traits.get_mode();
281  stream.print(R"(<input type=")");
282  if (mode == 2) {
283  stream.print(R"(password)");
284  } else { // default
285  stream.print(R"(text)");
286  }
287  stream.print(R"(" minlength=")");
288  stream.print(text->traits.get_min_length());
289  stream.print(R"(" maxlength=")");
290  stream.print(text->traits.get_max_length());
291  stream.print(R"(" pattern=")");
292  stream.print(text->traits.get_pattern().c_str());
293  stream.print(R"(" value=")");
294  stream.print(text->state.c_str());
295  stream.print(R"("/>)");
296  });
297  }
298  }
299 #endif
300 
301 #ifdef USE_SELECT
302  for (auto *obj : App.get_selects()) {
303  if (this->include_internal_ || !obj->is_internal()) {
304  write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) {
305  select::Select *select = (select::Select *) obj;
306  stream.print("<select>");
307  stream.print("<option></option>");
308  for (auto const &option : select->traits.get_options()) {
309  stream.print("<option>");
310  stream.print(option.c_str());
311  stream.print("</option>");
312  }
313  stream.print("</select>");
314  });
315  }
316  }
317 #endif
318 
319 #ifdef USE_LOCK
320  for (auto *obj : App.get_locks()) {
321  if (this->include_internal_ || !obj->is_internal()) {
322  write_row(stream, obj, "lock", "", [](AsyncResponseStream &stream, EntityBase *obj) {
323  lock::Lock *lock = (lock::Lock *) obj;
324  stream.print("<button>Lock</button><button>Unlock</button>");
325  if (lock->traits.get_supports_open()) {
326  stream.print("<button>Open</button>");
327  }
328  });
329  }
330  }
331 #endif
332 
333 #ifdef USE_CLIMATE
334  for (auto *obj : App.get_climates()) {
335  if (this->include_internal_ || !obj->is_internal())
336  write_row(stream, obj, "climate", "");
337  }
338 #endif
339 
340  stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
341  "REST API documentation.</p>"));
342  if (this->allow_ota_) {
343  stream->print(
344  F("<h2>OTA Update</h2><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
345  "type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
346  }
347  stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>"));
348 #ifdef USE_WEBSERVER_JS_INCLUDE
349  if (this->js_include_ != nullptr) {
350  stream->print(F("<script type=\"module\" src=\"/0.js\"></script>"));
351  }
352 #endif
353  if (strlen(this->js_url_) > 0) {
354  stream->print(F("<script src=\""));
355  stream->print(this->js_url_);
356  stream->print(F("\"></script>"));
357  }
358  stream->print(F("</article></body></html>"));
359  request->send(stream);
360 }
361 #elif USE_WEBSERVER_VERSION >= 2
362 void WebServer::handle_index_request(AsyncWebServerRequest *request) {
363  AsyncWebServerResponse *response =
364  request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE);
365  // No gzip header here because the HTML file is so small
366  request->send(response);
367 }
368 #endif
369 
370 #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
371 void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) {
372  AsyncWebServerResponse *response = request->beginResponse(200, "");
373  response->addHeader(HEADER_CORS_ALLOW_PNA, "true");
374  response->addHeader(HEADER_PNA_NAME, App.get_name().c_str());
375  std::string mac = get_mac_address_pretty();
376  response->addHeader(HEADER_PNA_ID, mac.c_str());
377  request->send(response);
378 }
379 #endif
380 
381 #ifdef USE_WEBSERVER_CSS_INCLUDE
382 void WebServer::handle_css_request(AsyncWebServerRequest *request) {
383  AsyncWebServerResponse *response =
384  request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE);
385  response->addHeader("Content-Encoding", "gzip");
386  request->send(response);
387 }
388 #endif
389 
390 #ifdef USE_WEBSERVER_JS_INCLUDE
391 void WebServer::handle_js_request(AsyncWebServerRequest *request) {
392  AsyncWebServerResponse *response =
393  request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE);
394  response->addHeader("Content-Encoding", "gzip");
395  request->send(response);
396 }
397 #endif
398 
399 #define set_json_id(root, obj, sensor, start_config) \
400  (root)["id"] = sensor; \
401  if (((start_config) == DETAIL_ALL)) { \
402  (root)["name"] = (obj)->get_name(); \
403  (root)["icon"] = (obj)->get_icon(); \
404  (root)["entity_category"] = (obj)->get_entity_category(); \
405  if ((obj)->is_disabled_by_default()) \
406  (root)["is_disabled_by_default"] = (obj)->is_disabled_by_default(); \
407  }
408 
409 #define set_json_value(root, obj, sensor, value, start_config) \
410  set_json_id((root), (obj), sensor, start_config); \
411  (root)["value"] = value;
412 
413 #define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \
414  set_json_value(root, obj, sensor, value, start_config); \
415  (root)["state"] = state;
416 
417 #ifdef USE_SENSOR
419  this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
420 }
421 void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
422  for (sensor::Sensor *obj : App.get_sensors()) {
423  if (obj->get_object_id() != match.id)
424  continue;
425  std::string data = this->sensor_json(obj, obj->state, DETAIL_STATE);
426  request->send(200, "application/json", data.c_str());
427  return;
428  }
429  request->send(404);
430 }
431 std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) {
432  return json::build_json([obj, value, start_config](JsonObject root) {
433  std::string state;
434  if (std::isnan(value)) {
435  state = "NA";
436  } else {
437  state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
438  if (!obj->get_unit_of_measurement().empty())
439  state += " " + obj->get_unit_of_measurement();
440  }
441  set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config);
442  if (start_config == DETAIL_ALL) {
443  if (!obj->get_unit_of_measurement().empty())
444  root["uom"] = obj->get_unit_of_measurement();
445  }
446  });
447 }
448 #endif
449 
450 #ifdef USE_TEXT_SENSOR
452  this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
453 }
454 void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
456  if (obj->get_object_id() != match.id)
457  continue;
458  std::string data = this->text_sensor_json(obj, obj->state, DETAIL_STATE);
459  request->send(200, "application/json", data.c_str());
460  return;
461  }
462  request->send(404);
463 }
464 std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value,
465  JsonDetail start_config) {
466  return json::build_json([obj, value, start_config](JsonObject root) {
467  set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config);
468  });
469 }
470 #endif
471 
472 #ifdef USE_SWITCH
474  this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state");
475 }
476 std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
477  return json::build_json([obj, value, start_config](JsonObject root) {
478  set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
479  if (start_config == DETAIL_ALL) {
480  root["assumed_state"] = obj->assumed_state();
481  }
482  });
483 }
484 void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
485  for (switch_::Switch *obj : App.get_switches()) {
486  if (obj->get_object_id() != match.id)
487  continue;
488 
489  if (request->method() == HTTP_GET && match.method.empty()) {
490  std::string data = this->switch_json(obj, obj->state, DETAIL_STATE);
491  request->send(200, "application/json", data.c_str());
492  } else if (match.method == "toggle") {
493  this->schedule_([obj]() { obj->toggle(); });
494  request->send(200);
495  } else if (match.method == "turn_on") {
496  this->schedule_([obj]() { obj->turn_on(); });
497  request->send(200);
498  } else if (match.method == "turn_off") {
499  this->schedule_([obj]() { obj->turn_off(); });
500  request->send(200);
501  } else {
502  request->send(404);
503  }
504  return;
505  }
506  request->send(404);
507 }
508 #endif
509 
510 #ifdef USE_BUTTON
511 std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) {
512  return json::build_json(
513  [obj, start_config](JsonObject root) { set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); });
514 }
515 
516 void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
517  for (button::Button *obj : App.get_buttons()) {
518  if (obj->get_object_id() != match.id)
519  continue;
520  if (match.method == "press") {
521  this->schedule_([obj]() { obj->press(); });
522  request->send(200);
523  return;
524  } else {
525  request->send(404);
526  }
527  return;
528  }
529  request->send(404);
530 }
531 #endif
532 
533 #ifdef USE_BINARY_SENSOR
535  this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
536 }
537 std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
538  return json::build_json([obj, value, start_config](JsonObject root) {
539  set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value,
540  start_config);
541  });
542 }
543 void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
545  if (obj->get_object_id() != match.id)
546  continue;
547  std::string data = this->binary_sensor_json(obj, obj->state, DETAIL_STATE);
548  request->send(200, "application/json", data.c_str());
549  return;
550  }
551  request->send(404);
552 }
553 #endif
554 
555 #ifdef USE_FAN
556 void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); }
557 std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
558  return json::build_json([obj, start_config](JsonObject root) {
559  set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state,
560  start_config);
561  const auto traits = obj->get_traits();
562  if (traits.supports_speed()) {
563  root["speed_level"] = obj->speed;
564  root["speed_count"] = traits.supported_speed_count();
565  }
566  if (obj->get_traits().supports_oscillation())
567  root["oscillation"] = obj->oscillating;
568  });
569 }
570 void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
571  for (fan::Fan *obj : App.get_fans()) {
572  if (obj->get_object_id() != match.id)
573  continue;
574 
575  if (request->method() == HTTP_GET && match.method.empty()) {
576  std::string data = this->fan_json(obj, DETAIL_STATE);
577  request->send(200, "application/json", data.c_str());
578  } else if (match.method == "toggle") {
579  this->schedule_([obj]() { obj->toggle().perform(); });
580  request->send(200);
581  } else if (match.method == "turn_on") {
582  auto call = obj->turn_on();
583  if (request->hasParam("speed_level")) {
584  auto speed_level = request->getParam("speed_level")->value();
585  auto val = parse_number<int>(speed_level.c_str());
586  if (!val.has_value()) {
587  ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str());
588  return;
589  }
590  call.set_speed(*val);
591  }
592  if (request->hasParam("oscillation")) {
593  auto speed = request->getParam("oscillation")->value();
594  auto val = parse_on_off(speed.c_str());
595  switch (val) {
596  case PARSE_ON:
597  call.set_oscillating(true);
598  break;
599  case PARSE_OFF:
600  call.set_oscillating(false);
601  break;
602  case PARSE_TOGGLE:
603  call.set_oscillating(!obj->oscillating);
604  break;
605  case PARSE_NONE:
606  request->send(404);
607  return;
608  }
609  }
610  this->schedule_([call]() mutable { call.perform(); });
611  request->send(200);
612  } else if (match.method == "turn_off") {
613  this->schedule_([obj]() { obj->turn_off().perform(); });
614  request->send(200);
615  } else {
616  request->send(404);
617  }
618  return;
619  }
620  request->send(404);
621 }
622 #endif
623 
624 #ifdef USE_LIGHT
626  this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state");
627 }
628 void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
629  for (light::LightState *obj : App.get_lights()) {
630  if (obj->get_object_id() != match.id)
631  continue;
632 
633  if (request->method() == HTTP_GET && match.method.empty()) {
634  std::string data = this->light_json(obj, DETAIL_STATE);
635  request->send(200, "application/json", data.c_str());
636  } else if (match.method == "toggle") {
637  this->schedule_([obj]() { obj->toggle().perform(); });
638  request->send(200);
639  } else if (match.method == "turn_on") {
640  auto call = obj->turn_on();
641  if (request->hasParam("brightness")) {
642  auto brightness = parse_number<float>(request->getParam("brightness")->value().c_str());
643  if (brightness.has_value()) {
644  call.set_brightness(*brightness / 255.0f);
645  }
646  }
647  if (request->hasParam("r")) {
648  auto r = parse_number<float>(request->getParam("r")->value().c_str());
649  if (r.has_value()) {
650  call.set_red(*r / 255.0f);
651  }
652  }
653  if (request->hasParam("g")) {
654  auto g = parse_number<float>(request->getParam("g")->value().c_str());
655  if (g.has_value()) {
656  call.set_green(*g / 255.0f);
657  }
658  }
659  if (request->hasParam("b")) {
660  auto b = parse_number<float>(request->getParam("b")->value().c_str());
661  if (b.has_value()) {
662  call.set_blue(*b / 255.0f);
663  }
664  }
665  if (request->hasParam("white_value")) {
666  auto white_value = parse_number<float>(request->getParam("white_value")->value().c_str());
667  if (white_value.has_value()) {
668  call.set_white(*white_value / 255.0f);
669  }
670  }
671  if (request->hasParam("color_temp")) {
672  auto color_temp = parse_number<float>(request->getParam("color_temp")->value().c_str());
673  if (color_temp.has_value()) {
674  call.set_color_temperature(*color_temp);
675  }
676  }
677  if (request->hasParam("flash")) {
678  auto flash = parse_number<uint32_t>(request->getParam("flash")->value().c_str());
679  if (flash.has_value()) {
680  call.set_flash_length(*flash * 1000);
681  }
682  }
683  if (request->hasParam("transition")) {
684  auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
685  if (transition.has_value()) {
686  call.set_transition_length(*transition * 1000);
687  }
688  }
689  if (request->hasParam("effect")) {
690  const char *effect = request->getParam("effect")->value().c_str();
691  call.set_effect(effect);
692  }
693 
694  this->schedule_([call]() mutable { call.perform(); });
695  request->send(200);
696  } else if (match.method == "turn_off") {
697  auto call = obj->turn_off();
698  if (request->hasParam("transition")) {
699  auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
700  if (transition.has_value()) {
701  call.set_transition_length(*transition * 1000);
702  }
703  }
704  this->schedule_([call]() mutable { call.perform(); });
705  request->send(200);
706  } else {
707  request->send(404);
708  }
709  return;
710  }
711  request->send(404);
712 }
713 std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) {
714  return json::build_json([obj, start_config](JsonObject root) {
715  set_json_id(root, obj, "light-" + obj->get_object_id(), start_config);
716  root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
717 
719  if (start_config == DETAIL_ALL) {
720  JsonArray opt = root.createNestedArray("effects");
721  opt.add("None");
722  for (auto const &option : obj->get_effects()) {
723  opt.add(option->get_name());
724  }
725  }
726  });
727 }
728 #endif
729 
730 #ifdef USE_COVER
732  this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state");
733 }
734 void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
735  for (cover::Cover *obj : App.get_covers()) {
736  if (obj->get_object_id() != match.id)
737  continue;
738 
739  if (request->method() == HTTP_GET && match.method.empty()) {
740  std::string data = this->cover_json(obj, DETAIL_STATE);
741  request->send(200, "application/json", data.c_str());
742  continue;
743  }
744 
745  auto call = obj->make_call();
746  if (match.method == "open") {
747  call.set_command_open();
748  } else if (match.method == "close") {
749  call.set_command_close();
750  } else if (match.method == "stop") {
751  call.set_command_stop();
752  } else if (match.method == "toggle") {
753  call.set_command_toggle();
754  } else if (match.method != "set") {
755  request->send(404);
756  return;
757  }
758 
759  auto traits = obj->get_traits();
760  if ((request->hasParam("position") && !traits.get_supports_position()) ||
761  (request->hasParam("tilt") && !traits.get_supports_tilt())) {
762  request->send(409);
763  return;
764  }
765 
766  if (request->hasParam("position")) {
767  auto position = parse_number<float>(request->getParam("position")->value().c_str());
768  if (position.has_value()) {
769  call.set_position(*position);
770  }
771  }
772  if (request->hasParam("tilt")) {
773  auto tilt = parse_number<float>(request->getParam("tilt")->value().c_str());
774  if (tilt.has_value()) {
775  call.set_tilt(*tilt);
776  }
777  }
778 
779  this->schedule_([call]() mutable { call.perform(); });
780  request->send(200);
781  return;
782  }
783  request->send(404);
784 }
785 std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
786  return json::build_json([obj, start_config](JsonObject root) {
787  set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
788  obj->position, start_config);
789  root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
790 
791  if (obj->get_traits().get_supports_position())
792  root["position"] = obj->position;
793  if (obj->get_traits().get_supports_tilt())
794  root["tilt"] = obj->tilt;
795  });
796 }
797 #endif
798 
799 #ifdef USE_NUMBER
801  this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state");
802 }
803 void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
804  for (auto *obj : App.get_numbers()) {
805  if (obj->get_object_id() != match.id)
806  continue;
807 
808  if (request->method() == HTTP_GET && match.method.empty()) {
809  std::string data = this->number_json(obj, obj->state, DETAIL_STATE);
810  request->send(200, "application/json", data.c_str());
811  return;
812  }
813  if (match.method != "set") {
814  request->send(404);
815  return;
816  }
817 
818  auto call = obj->make_call();
819  if (request->hasParam("value")) {
820  auto value = parse_number<float>(request->getParam("value")->value().c_str());
821  if (value.has_value())
822  call.set_value(*value);
823  }
824 
825  this->schedule_([call]() mutable { call.perform(); });
826  request->send(200);
827  return;
828  }
829  request->send(404);
830 }
831 
832 std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) {
833  return json::build_json([obj, value, start_config](JsonObject root) {
834  set_json_id(root, obj, "number-" + obj->get_object_id(), start_config);
835  if (start_config == DETAIL_ALL) {
836  root["min_value"] = obj->traits.get_min_value();
837  root["max_value"] = obj->traits.get_max_value();
838  root["step"] = obj->traits.get_step();
839  root["mode"] = (int) obj->traits.get_mode();
840  if (!obj->traits.get_unit_of_measurement().empty())
841  root["uom"] = obj->traits.get_unit_of_measurement();
842  }
843  if (std::isnan(value)) {
844  root["value"] = "\"NaN\"";
845  root["state"] = "NA";
846  } else {
847  root["value"] = value;
849  if (!obj->traits.get_unit_of_measurement().empty())
850  state += " " + obj->traits.get_unit_of_measurement();
851  root["state"] = state;
852  }
853  });
854 }
855 #endif
856 
857 #ifdef USE_DATETIME_DATE
859  this->events_.send(this->date_json(obj, DETAIL_STATE).c_str(), "state");
860 }
861 void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
862  for (auto *obj : App.get_dates()) {
863  if (obj->get_object_id() != match.id)
864  continue;
865  if (request->method() == HTTP_GET) {
866  std::string data = this->date_json(obj, DETAIL_STATE);
867  request->send(200, "application/json", data.c_str());
868  return;
869  }
870  if (match.method != "set") {
871  request->send(404);
872  return;
873  }
874 
875  auto call = obj->make_call();
876 
877  if (!request->hasParam("value")) {
878  request->send(409);
879  return;
880  }
881 
882  if (request->hasParam("value")) {
883  std::string value = request->getParam("value")->value().c_str();
884  call.set_date(value);
885  }
886 
887  this->schedule_([call]() mutable { call.perform(); });
888  request->send(200);
889  return;
890  }
891  request->send(404);
892 }
893 
894 std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) {
895  return json::build_json([obj, start_config](JsonObject root) {
896  set_json_id(root, obj, "date-" + obj->get_object_id(), start_config);
897  std::string value = str_sprintf("%d-%d-%d", obj->year, obj->month, obj->day);
898  root["value"] = value;
899  root["state"] = value;
900  });
901 }
902 #endif // USE_DATETIME_DATE
903 
904 #ifdef USE_TEXT
905 void WebServer::on_text_update(text::Text *obj, const std::string &state) {
906  this->events_.send(this->text_json(obj, state, DETAIL_STATE).c_str(), "state");
907 }
908 void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
909  for (auto *obj : App.get_texts()) {
910  if (obj->get_object_id() != match.id)
911  continue;
912 
913  if (request->method() == HTTP_GET && match.method.empty()) {
914  std::string data = this->text_json(obj, obj->state, DETAIL_STATE);
915  request->send(200, "text/json", data.c_str());
916  return;
917  }
918  if (match.method != "set") {
919  request->send(404);
920  return;
921  }
922 
923  auto call = obj->make_call();
924  if (request->hasParam("value")) {
925  String value = request->getParam("value")->value();
926  call.set_value(value.c_str());
927  }
928 
929  this->defer([call]() mutable { call.perform(); });
930  request->send(200);
931  return;
932  }
933  request->send(404);
934 }
935 
936 std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) {
937  return json::build_json([obj, value, start_config](JsonObject root) {
938  set_json_id(root, obj, "text-" + obj->get_object_id(), start_config);
939  if (start_config == DETAIL_ALL) {
940  root["mode"] = (int) obj->traits.get_mode();
941  }
942  root["min_length"] = obj->traits.get_min_length();
943  root["max_length"] = obj->traits.get_max_length();
944  root["pattern"] = obj->traits.get_pattern();
946  root["state"] = "********";
947  } else {
948  root["state"] = value;
949  }
950  root["value"] = value;
951  });
952 }
953 #endif
954 
955 #ifdef USE_SELECT
956 void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
957  this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
958 }
959 void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
960  for (auto *obj : App.get_selects()) {
961  if (obj->get_object_id() != match.id)
962  continue;
963 
964  if (request->method() == HTTP_GET && match.method.empty()) {
965  auto detail = DETAIL_STATE;
966  auto *param = request->getParam("detail");
967  if (param && param->value() == "all") {
968  detail = DETAIL_ALL;
969  }
970  std::string data = this->select_json(obj, obj->state, detail);
971  request->send(200, "application/json", data.c_str());
972  return;
973  }
974 
975  if (match.method != "set") {
976  request->send(404);
977  return;
978  }
979 
980  auto call = obj->make_call();
981 
982  if (request->hasParam("option")) {
983  auto option = request->getParam("option")->value();
984  call.set_option(option.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations)
985  }
986 
987  this->schedule_([call]() mutable { call.perform(); });
988  request->send(200);
989  return;
990  }
991  request->send(404);
992 }
993 std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) {
994  return json::build_json([obj, value, start_config](JsonObject root) {
995  set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config);
996  if (start_config == DETAIL_ALL) {
997  JsonArray opt = root.createNestedArray("option");
998  for (auto &option : obj->traits.get_options()) {
999  opt.add(option);
1000  }
1001  }
1002  });
1003 }
1004 #endif
1005 
1006 // Longest: HORIZONTAL
1007 #define PSTR_LOCAL(mode_s) strncpy_P(buf, (PGM_P) ((mode_s)), 15)
1008 
1009 #ifdef USE_CLIMATE
1011  this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
1012 }
1013 
1014 void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1015  for (auto *obj : App.get_climates()) {
1016  if (obj->get_object_id() != match.id)
1017  continue;
1018 
1019  if (request->method() == HTTP_GET && match.method.empty()) {
1020  std::string data = this->climate_json(obj, DETAIL_STATE);
1021  request->send(200, "application/json", data.c_str());
1022  return;
1023  }
1024 
1025  if (match.method != "set") {
1026  request->send(404);
1027  return;
1028  }
1029 
1030  auto call = obj->make_call();
1031 
1032  if (request->hasParam("mode")) {
1033  auto mode = request->getParam("mode")->value();
1034  call.set_mode(mode.c_str());
1035  }
1036 
1037  if (request->hasParam("target_temperature_high")) {
1038  auto target_temperature_high = parse_number<float>(request->getParam("target_temperature_high")->value().c_str());
1039  if (target_temperature_high.has_value())
1040  call.set_target_temperature_high(*target_temperature_high);
1041  }
1042 
1043  if (request->hasParam("target_temperature_low")) {
1044  auto target_temperature_low = parse_number<float>(request->getParam("target_temperature_low")->value().c_str());
1045  if (target_temperature_low.has_value())
1046  call.set_target_temperature_low(*target_temperature_low);
1047  }
1048 
1049  if (request->hasParam("target_temperature")) {
1050  auto target_temperature = parse_number<float>(request->getParam("target_temperature")->value().c_str());
1051  if (target_temperature.has_value())
1052  call.set_target_temperature(*target_temperature);
1053  }
1054 
1055  this->schedule_([call]() mutable { call.perform(); });
1056  request->send(200);
1057  return;
1058  }
1059  request->send(404);
1060 }
1061 
1062 std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
1063  return json::build_json([obj, start_config](JsonObject root) {
1064  set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
1065  const auto traits = obj->get_traits();
1066  int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
1067  int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
1068  char buf[16];
1069 
1070  if (start_config == DETAIL_ALL) {
1071  JsonArray opt = root.createNestedArray("modes");
1072  for (climate::ClimateMode m : traits.get_supported_modes())
1073  opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m)));
1074  if (!traits.get_supported_custom_fan_modes().empty()) {
1075  JsonArray opt = root.createNestedArray("fan_modes");
1076  for (climate::ClimateFanMode m : traits.get_supported_fan_modes())
1077  opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m)));
1078  }
1079 
1080  if (!traits.get_supported_custom_fan_modes().empty()) {
1081  JsonArray opt = root.createNestedArray("custom_fan_modes");
1082  for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
1083  opt.add(custom_fan_mode);
1084  }
1085  if (traits.get_supports_swing_modes()) {
1086  JsonArray opt = root.createNestedArray("swing_modes");
1087  for (auto swing_mode : traits.get_supported_swing_modes())
1088  opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode)));
1089  }
1090  if (traits.get_supports_presets() && obj->preset.has_value()) {
1091  JsonArray opt = root.createNestedArray("presets");
1092  for (climate::ClimatePreset m : traits.get_supported_presets())
1093  opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m)));
1094  }
1095  if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
1096  JsonArray opt = root.createNestedArray("custom_presets");
1097  for (auto const &custom_preset : traits.get_supported_custom_presets())
1098  opt.add(custom_preset);
1099  }
1100  }
1101 
1102  bool has_state = false;
1103  root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode));
1104  root["max_temp"] = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy);
1105  root["min_temp"] = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy);
1106  root["step"] = traits.get_visual_target_temperature_step();
1107  if (traits.get_supports_action()) {
1108  root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action));
1109  root["state"] = root["action"];
1110  has_state = true;
1111  }
1112  if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) {
1113  root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value()));
1114  }
1115  if (!traits.get_supported_custom_fan_modes().empty() && obj->custom_fan_mode.has_value()) {
1116  root["custom_fan_mode"] = obj->custom_fan_mode.value().c_str();
1117  }
1118  if (traits.get_supports_presets() && obj->preset.has_value()) {
1119  root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value()));
1120  }
1121  if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
1122  root["custom_preset"] = obj->custom_preset.value().c_str();
1123  }
1124  if (traits.get_supports_swing_modes()) {
1125  root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode));
1126  }
1127  if (traits.get_supports_current_temperature()) {
1128  if (!std::isnan(obj->current_temperature)) {
1129  root["current_temperature"] = value_accuracy_to_string(obj->current_temperature, current_accuracy);
1130  } else {
1131  root["current_temperature"] = "NA";
1132  }
1133  }
1134  if (traits.get_supports_two_point_target_temperature()) {
1135  root["target_temperature_low"] = value_accuracy_to_string(obj->target_temperature_low, target_accuracy);
1136  root["target_temperature_high"] = value_accuracy_to_string(obj->target_temperature_high, target_accuracy);
1137  if (!has_state) {
1138  root["state"] = value_accuracy_to_string((obj->target_temperature_high + obj->target_temperature_low) / 2.0f,
1139  target_accuracy);
1140  }
1141  } else {
1142  root["target_temperature"] = value_accuracy_to_string(obj->target_temperature, target_accuracy);
1143  if (!has_state)
1144  root["state"] = root["target_temperature"];
1145  }
1146  });
1147 }
1148 #endif
1149 
1150 #ifdef USE_LOCK
1152  this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state");
1153 }
1154 std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
1155  return json::build_json([obj, value, start_config](JsonObject root) {
1156  set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value,
1157  start_config);
1158  });
1159 }
1160 void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1161  for (lock::Lock *obj : App.get_locks()) {
1162  if (obj->get_object_id() != match.id)
1163  continue;
1164 
1165  if (request->method() == HTTP_GET && match.method.empty()) {
1166  std::string data = this->lock_json(obj, obj->state, DETAIL_STATE);
1167  request->send(200, "application/json", data.c_str());
1168  } else if (match.method == "lock") {
1169  this->schedule_([obj]() { obj->lock(); });
1170  request->send(200);
1171  } else if (match.method == "unlock") {
1172  this->schedule_([obj]() { obj->unlock(); });
1173  request->send(200);
1174  } else if (match.method == "open") {
1175  this->schedule_([obj]() { obj->open(); });
1176  request->send(200);
1177  } else {
1178  request->send(404);
1179  }
1180  return;
1181  }
1182  request->send(404);
1183 }
1184 #endif
1185 
1186 #ifdef USE_ALARM_CONTROL_PANEL
1188  this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state");
1189 }
1192  JsonDetail start_config) {
1193  return json::build_json([obj, value, start_config](JsonObject root) {
1194  char buf[16];
1195  set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(),
1196  PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config);
1197  });
1198 }
1199 void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1201  if (obj->get_object_id() != match.id)
1202  continue;
1203 
1204  if (request->method() == HTTP_GET && match.method.empty()) {
1205  std::string data = this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE);
1206  request->send(200, "application/json", data.c_str());
1207  return;
1208  }
1209  }
1210  request->send(404);
1211 }
1212 #endif
1213 
1214 bool WebServer::canHandle(AsyncWebServerRequest *request) {
1215  if (request->url() == "/")
1216  return true;
1217 
1218 #ifdef USE_WEBSERVER_CSS_INCLUDE
1219  if (request->url() == "/0.css")
1220  return true;
1221 #endif
1222 
1223 #ifdef USE_WEBSERVER_JS_INCLUDE
1224  if (request->url() == "/0.js")
1225  return true;
1226 #endif
1227 
1228 #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
1229  if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) {
1230 #ifdef USE_ARDUINO
1231  // Header needs to be added to interesting header list for it to not be
1232  // nuked by the time we handle the request later.
1233  // Only required in Arduino framework.
1234  request->addInterestingHeader(HEADER_CORS_REQ_PNA);
1235 #endif
1236  return true;
1237  }
1238 #endif
1239 
1240  UrlMatch match = match_url(request->url().c_str(), true);
1241  if (!match.valid)
1242  return false;
1243 #ifdef USE_SENSOR
1244  if (request->method() == HTTP_GET && match.domain == "sensor")
1245  return true;
1246 #endif
1247 
1248 #ifdef USE_SWITCH
1249  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "switch")
1250  return true;
1251 #endif
1252 
1253 #ifdef USE_BUTTON
1254  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "button")
1255  return true;
1256 #endif
1257 
1258 #ifdef USE_BINARY_SENSOR
1259  if (request->method() == HTTP_GET && match.domain == "binary_sensor")
1260  return true;
1261 #endif
1262 
1263 #ifdef USE_FAN
1264  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "fan")
1265  return true;
1266 #endif
1267 
1268 #ifdef USE_LIGHT
1269  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "light")
1270  return true;
1271 #endif
1272 
1273 #ifdef USE_TEXT_SENSOR
1274  if (request->method() == HTTP_GET && match.domain == "text_sensor")
1275  return true;
1276 #endif
1277 
1278 #ifdef USE_COVER
1279  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "cover")
1280  return true;
1281 #endif
1282 
1283 #ifdef USE_NUMBER
1284  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "number")
1285  return true;
1286 #endif
1287 
1288 #ifdef USE_DATETIME_DATE
1289  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "date")
1290  return true;
1291 #endif
1292 
1293 #ifdef USE_TEXT
1294  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text")
1295  return true;
1296 #endif
1297 
1298 #ifdef USE_SELECT
1299  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "select")
1300  return true;
1301 #endif
1302 
1303 #ifdef USE_CLIMATE
1304  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate")
1305  return true;
1306 #endif
1307 
1308 #ifdef USE_LOCK
1309  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
1310  return true;
1311 #endif
1312 
1313 #ifdef USE_ALARM_CONTROL_PANEL
1314  if (request->method() == HTTP_GET && match.domain == "alarm_control_panel")
1315  return true;
1316 #endif
1317 
1318  return false;
1319 }
1320 void WebServer::handleRequest(AsyncWebServerRequest *request) {
1321  if (request->url() == "/") {
1322  this->handle_index_request(request);
1323  return;
1324  }
1325 
1326 #ifdef USE_WEBSERVER_CSS_INCLUDE
1327  if (request->url() == "/0.css") {
1328  this->handle_css_request(request);
1329  return;
1330  }
1331 #endif
1332 
1333 #ifdef USE_WEBSERVER_JS_INCLUDE
1334  if (request->url() == "/0.js") {
1335  this->handle_js_request(request);
1336  return;
1337  }
1338 #endif
1339 
1340 #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
1341  if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) {
1342  this->handle_pna_cors_request(request);
1343  return;
1344  }
1345 #endif
1346 
1347  UrlMatch match = match_url(request->url().c_str());
1348 #ifdef USE_SENSOR
1349  if (match.domain == "sensor") {
1350  this->handle_sensor_request(request, match);
1351  return;
1352  }
1353 #endif
1354 
1355 #ifdef USE_SWITCH
1356  if (match.domain == "switch") {
1357  this->handle_switch_request(request, match);
1358  return;
1359  }
1360 #endif
1361 
1362 #ifdef USE_BUTTON
1363  if (match.domain == "button") {
1364  this->handle_button_request(request, match);
1365  return;
1366  }
1367 #endif
1368 
1369 #ifdef USE_BINARY_SENSOR
1370  if (match.domain == "binary_sensor") {
1371  this->handle_binary_sensor_request(request, match);
1372  return;
1373  }
1374 #endif
1375 
1376 #ifdef USE_FAN
1377  if (match.domain == "fan") {
1378  this->handle_fan_request(request, match);
1379  return;
1380  }
1381 #endif
1382 
1383 #ifdef USE_LIGHT
1384  if (match.domain == "light") {
1385  this->handle_light_request(request, match);
1386  return;
1387  }
1388 #endif
1389 
1390 #ifdef USE_TEXT_SENSOR
1391  if (match.domain == "text_sensor") {
1392  this->handle_text_sensor_request(request, match);
1393  return;
1394  }
1395 #endif
1396 
1397 #ifdef USE_COVER
1398  if (match.domain == "cover") {
1399  this->handle_cover_request(request, match);
1400  return;
1401  }
1402 #endif
1403 
1404 #ifdef USE_NUMBER
1405  if (match.domain == "number") {
1406  this->handle_number_request(request, match);
1407  return;
1408  }
1409 #endif
1410 
1411 #ifdef USE_DATETIME_DATE
1412  if (match.domain == "date") {
1413  this->handle_date_request(request, match);
1414  return;
1415  }
1416 #endif
1417 
1418 #ifdef USE_TEXT
1419  if (match.domain == "text") {
1420  this->handle_text_request(request, match);
1421  return;
1422  }
1423 #endif
1424 
1425 #ifdef USE_SELECT
1426  if (match.domain == "select") {
1427  this->handle_select_request(request, match);
1428  return;
1429  }
1430 #endif
1431 
1432 #ifdef USE_CLIMATE
1433  if (match.domain == "climate") {
1434  this->handle_climate_request(request, match);
1435  return;
1436  }
1437 #endif
1438 
1439 #ifdef USE_LOCK
1440  if (match.domain == "lock") {
1441  this->handle_lock_request(request, match);
1442 
1443  return;
1444  }
1445 #endif
1446 
1447 #ifdef USE_ALARM_CONTROL_PANEL
1448  if (match.domain == "alarm_control_panel") {
1449  this->handle_alarm_control_panel_request(request, match);
1450 
1451  return;
1452  }
1453 #endif
1454 }
1455 
1456 bool WebServer::isRequestHandlerTrivial() { return false; }
1457 
1458 void WebServer::schedule_(std::function<void()> &&f) {
1459 #ifdef USE_ESP32
1460  xSemaphoreTake(this->to_schedule_lock_, portMAX_DELAY);
1461  to_schedule_.push_back(std::move(f));
1462  xSemaphoreGive(this->to_schedule_lock_);
1463 #else
1464  this->defer(std::move(f));
1465 #endif
1466 }
1467 
1468 } // namespace web_server
1469 } // namespace esphome
Base class for all switches.
Definition: switch.h:39
value_type const & value() const
Definition: optional.h:89
bool state
The current on/off state of the fan.
Definition: fan.h:110
const size_t ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE
ClimateSwingMode swing_mode
The active swing mode of the climate device.
Definition: climate.h:202
float target_temperature_low
Definition: climate.h:585
void handle_pna_cors_request(AsyncWebServerRequest *request)
Definition: web_server.cpp:371
AlarmControlPanelState get_state() const
Get the state.
void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a number request under &#39;/number/<id>&#39;.
Definition: web_server.cpp:803
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition: light_state.h:34
bool oscillating
The current oscillation state of the fan.
Definition: fan.h:112
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:52
std::string number_json(number::Number *obj, float value, JsonDetail start_config)
Dump the number state with its value as a JSON string.
Definition: web_server.cpp:832
std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config)
Dump the sensor state with its value as a JSON string.
Definition: web_server.cpp:431
void add_on_log_callback(std::function< void(int, const char *, const char *)> &&callback)
Register a callback that will be called for every log message sent.
Definition: logger.cpp:159
void on_sensor_update(sensor::Sensor *obj, float state) override
Definition: web_server.cpp:418
bool is_on() const
Get the binary true/false state of these light color values.
Base class for all cover devices.
Definition: cover.h:111
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:412
WebServer(web_server_base::WebServerBase *base)
Definition: web_server.cpp:94
void handleRequest(AsyncWebServerRequest *request) override
Override the web handler&#39;s handleRequest method.
TextMode get_mode() const
Definition: text_traits.h:29
ClimatePreset
Enum for all preset modes.
Definition: climate_mode.h:82
std::string state
Definition: text.h:26
const std::vector< climate::Climate * > & get_climates()
Definition: application.h:282
float target_temperature
The target temperature of the climate device.
Definition: climate.h:186
SemaphoreHandle_t to_schedule_lock_
Definition: web_server.h:309
std::string get_use_address()
Get the active network hostname.
Definition: util.cpp:52
void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a binary sensor request under &#39;/binary_sensor/<id>&#39;.
Definition: web_server.cpp:543
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.
Definition: web_server.cpp:993
LockState state
The current reported state of the lock.
Definition: lock.h:122
const std::vector< alarm_control_panel::AlarmControlPanel * > & get_alarm_control_panels()
Definition: application.h:346
const char * lock_state_to_string(LockState state)
Definition: lock.cpp:9
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a select request under &#39;/select/<id>&#39;.
Definition: web_server.cpp:959
TextTraits traits
Definition: text.h:27
void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a text input request under &#39;/text/<id>&#39;.
Definition: web_server.cpp:908
CoverOperation current_operation
The current operation of the cover (idle, opening, closing).
Definition: cover.h:116
Base class for all buttons.
Definition: button.h:29
const LogString * climate_mode_to_string(ClimateMode mode)
Convert the given ClimateMode to a human-readable string.
Definition: climate_mode.cpp:6
virtual FanTraits get_traits()=0
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition: component.cpp:125
bool get_supports_position() const
Definition: cover_traits.h:12
ClimateMode mode
The active mode of the climate device.
Definition: climate.h:173
void on_lock_update(lock::Lock *obj) override
int speed
Definition: fan.h:35
virtual bool assumed_state()
Return whether this switch uses an assumed state - i.e.
Definition: switch.cpp:58
float tilt
Definition: cover.h:15
void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function< void(AsyncResponseStream &stream, EntityBase *obj)> &action_func=nullptr)
Definition: web_server.cpp:46
const std::string & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
Definition: application.h:170
void setup() override
Setup the internal web server and register handlers.
Definition: web_server.cpp:123
SelectTraits traits
Definition: select.h:34
float target_temperature_high
The maximum target temperature of the climate device, for climate devices with split target temperatu...
Definition: climate.h:191
float current_temperature
The current temperature of the climate device, as reported from the integration.
Definition: climate.h:179
mopeka_std_values val[4]
const std::vector< fan::Fan * > & get_fans()
Definition: application.h:255
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override
Definition: web_server.cpp:534
void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a light request under &#39;/light/<id>/</turn_on/turn_off/toggle>&#39;.
Definition: web_server.cpp:628
bool isRequestHandlerTrivial() override
This web handle is not trivial.
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a lock request under &#39;/lock/<id>/</lock/unlock/open>&#39;.
bool has_value() const
Definition: optional.h:87
float target_temperature_high
Definition: climate.h:586
void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a button request under &#39;/button/<id>/press&#39;.
Definition: web_server.cpp:516
int get_max_length() const
Definition: text_traits.h:21
Base-class for all text inputs.
Definition: text.h:24
bool supports_oscillation() const
Return if this fan supports oscillation.
Definition: fan_traits.h:16
void on_light_update(light::LightState *obj) override
Definition: web_server.cpp:625
float tilt
The current tilt value of the cover from 0.0 to 1.0.
Definition: cover.h:124
std::string get_object_id() const
Definition: entity_base.cpp:43
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
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:397
ClimateSwingMode swing_mode
Definition: climate.h:581
const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE
Internal helper struct that is used to parse incoming URLs.
Definition: web_server.h:35
LockTraits traits
Definition: lock.h:124
optional< std::string > custom_fan_mode
The active custom fan mode of the climate device.
Definition: climate.h:205
virtual CoverTraits get_traits()=0
void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a sensor request under &#39;/sensor/<id>&#39;.
Definition: web_server.cpp:421
const std::vector< lock::Lock * > & get_locks()
Definition: application.h:327
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.
Definition: web_server.cpp:464
const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE
std::string domain
The domain of the component, for example "sensor".
Definition: web_server.h:36
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.
Definition: web_server.cpp:936
Logger * global_logger
Definition: logger.cpp:179
uint8_t m
Definition: bl0939.h:20
void on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) override
Definition: web_server.cpp:451
const char *const TAG
Definition: spi.cpp:8
void add_handler(AsyncWebHandler *handler)
void set_css_include(const char *css_include)
Set local path to the script that&#39;s embedded in the index page.
Definition: web_server.cpp:107
void on_text_update(text::Text *obj, const std::string &state) override
Definition: web_server.cpp:905
const std::vector< button::Button * > & get_buttons()
Definition: application.h:228
void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a switch request under &#39;/switch/<id>/</turn_on/turn_off/toggle>&#39;.
Definition: web_server.cpp:484
const LogString * alarm_control_panel_state_to_string(AlarmControlPanelState state)
Returns a string representation of the state.
std::vector< std::string > get_options() const
optional< ClimatePreset > preset
The active preset of the climate device.
Definition: climate.h:208
uint8_t custom_preset
Definition: climate.h:579
UrlMatch match_url(const std::string &url, bool only_domain=false)
Definition: web_server.cpp:68
const std::vector< switch_::Switch * > & get_switches()
Definition: application.h:219
Base-class for all numbers.
Definition: number.h:39
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:312
const char * cover_operation_to_str(CoverOperation op)
Definition: cover.cpp:21
int speed
The current fan speed level.
Definition: fan.h:114
void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a alarm_control_panel request under &#39;/alarm_control_panel/<id>&#39;.
void handle_css_request(AsyncWebServerRequest *request)
Handle included css request under &#39;/0.css&#39;.
Definition: web_server.cpp:382
bool valid
Whether this match is valid.
Definition: web_server.h:39
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:151
bool is_internal() const
Definition: entity_base.cpp:22
bool is_fully_closed() const
Helper method to check if the cover is fully closed. Equivalent to comparing .position against 0...
Definition: cover.cpp:209
void on_select_update(select::Select *obj, const std::string &state, size_t index) override
Definition: web_server.cpp:956
ClimateTraits get_traits()
Get the traits of this climate device with all overrides applied.
Definition: climate.cpp:440
std::string get_unit_of_measurement()
Get the unit of measurement, using the manual override if set.
Definition: entity_base.cpp:87
const std::vector< text_sensor::TextSensor * > & get_text_sensors()
Definition: application.h:246
const LogString * climate_preset_to_string(ClimatePreset preset)
Convert the given PresetMode to a human-readable string.
int8_t get_target_temperature_accuracy_decimals() const
const std::vector< sensor::Sensor * > & get_sensors()
Definition: application.h:237
Application App
Global storage of Application pointer - only one Application can exist.
const std::vector< binary_sensor::BinarySensor * > & get_binary_sensors()
Definition: application.h:210
const std::vector< LightEffect * > & get_effects() const
Get all effects for this light state.
int8_t step_to_accuracy_decimals(float step)
Derive accuracy in decimals from an increment step.
Definition: helpers.cpp:423
std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config)
Dump the switch state with its value as a JSON string.
Definition: web_server.cpp:476
std::string build_json(const json_build_t &f)
Build a JSON string with the provided json build function.
Definition: json_util.cpp:21
void begin(bool include_internal=false)
const std::string & get_name() const
Get the name of this Application set by pre_setup().
Definition: application.h:167
std::string light_json(light::LightState *obj, JsonDetail start_config)
Dump the light state as a JSON string.
Definition: web_server.cpp:713
static void dump_json(LightState &state, JsonObject root)
Dump the state of a light as JSON.
const std::vector< text::Text * > & get_texts()
Definition: application.h:309
ClimateMode
Enum for all modes a climate device can be in.
Definition: climate_mode.h:10
void handle_index_request(AsyncWebServerRequest *request)
Handle an index request under &#39;/&#39;.
Definition: web_server.cpp:175
NumberTraits traits
Definition: number.h:49
void on_climate_update(climate::Climate *obj) override
const std::vector< cover::Cover * > & get_covers()
Definition: application.h:264
constexpr const char * c_str() const
Definition: string_ref.h:68
void set_js_url(const char *js_url)
Set the URL to the script that&#39;s embedded in the index page.
Definition: web_server.cpp:103
float get_setup_priority() const override
MQTT setup priority.
Definition: web_server.cpp:172
optional< std::string > custom_preset
The active custom preset mode of the climate device.
Definition: climate.h:211
const LogString * climate_fan_mode_to_string(ClimateFanMode fan_mode)
Convert the given ClimateFanMode to a human-readable string.
optional< ClimateFanMode > fan_mode
The active fan mode of the climate device.
Definition: climate.h:199
float position
The position of the cover from 0.0 (fully closed) to 1.0 (fully open).
Definition: cover.h:122
void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a fan request under &#39;/fan/<id>/</turn_on/turn_off/toggle>&#39;.
Definition: web_server.cpp:570
void set_css_url(const char *css_url)
Set the URL to the CSS <link> that&#39;s sent to each client.
Definition: web_server.cpp:102
std::string id
The id of the device that&#39;s being accessed, for example "living_room_fan".
Definition: web_server.h:37
const std::vector< light::LightState * > & get_lights()
Definition: application.h:273
void on_date_update(datetime::DateEntity *obj) override
Definition: web_server.cpp:858
std::string get_comment() const
Get the comment of this Application set by pre_setup().
Definition: application.h:176
std::string button_json(button::Button *obj, JsonDetail start_config)
Dump the button details with its value as a JSON string.
Definition: web_server.cpp:511
void on_cover_update(cover::Cover *obj) override
Definition: web_server.cpp:731
void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a cover request under &#39;/cover/<id>/<open/close/stop/set>&#39;.
Definition: web_server.cpp:734
void on_switch_update(switch_::Switch *obj, bool state) override
Definition: web_server.cpp:473
bool get_supports_tilt() const
Definition: cover_traits.h:14
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override
void setup_controller(bool include_internal=false)
Definition: controller.cpp:7
std::string date_json(datetime::DateEntity *obj, JsonDetail start_config)
Dump the date state with its value as a JSON string.
Definition: web_server.cpp:894
std::string get_config_json()
Return the webserver configuration as JSON.
Definition: web_server.cpp:113
Base-class for all selects.
Definition: select.h:31
void on_fan_update(fan::Fan *obj) override
Definition: web_server.cpp:556
web_server_base::WebServerBase * base_
Definition: web_server.h:291
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
void on_number_update(number::Number *obj, float state) override
Definition: web_server.cpp:800
std::string fan_json(fan::Fan *obj, JsonDetail start_config)
Dump the fan state as a JSON string.
Definition: web_server.cpp:557
Base class for all binary_sensor-type classes.
Definition: binary_sensor.h:37
LightColorValues remote_values
The remote color values reported to the frontend.
Definition: light_state.h:77
LockState
Enum for all states a lock can be in.
Definition: lock.h:26
NumberMode get_mode() const
Definition: number_traits.h:29
void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a climate request under &#39;/climate/<id>&#39;.
int8_t get_accuracy_decimals()
Get the accuracy in decimals, using the manual override if set.
Definition: sensor.cpp:25
const std::vector< datetime::DateEntity * > & get_dates()
Definition: application.h:300
int get_min_length() const
Definition: text_traits.h:19
float position
Definition: cover.h:14
const std::vector< select::Select * > & get_selects()
Definition: application.h:318
Base-class for all sensors.
Definition: sensor.h:57
std::string get_mac_address_pretty()
Get the device MAC address as a string, in colon-separated uppercase hex notation.
Definition: helpers.cpp:592
bool canHandle(AsyncWebServerRequest *request) override
Override the web handler&#39;s canHandle method.
AsyncEventSourceResponse AsyncEventSourceClient
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.
ListEntitiesIterator entities_iterator_
Definition: web_server.h:293
std::deque< std::function< void()> > to_schedule_
Definition: web_server.h:308
const std::vector< number::Number * > & get_numbers()
Definition: application.h:291
const LogString * climate_action_to_string(ClimateAction action)
Convert the given ClimateAction to a human-readable string.
void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a text sensor request under &#39;/text_sensor/<id>&#39;.
Definition: web_server.cpp:454
uint8_t custom_fan_mode
Definition: climate.h:574
bool get_supports_open() const
Definition: lock.h:40
float target_temperature
Definition: climate.h:583
void schedule_(std::function< void()> &&f)
std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config)
Dump the lock state with its value as a JSON string.
std::string get_pattern() const
Definition: text_traits.h:25
float target_temperature_low
The minimum target temperature of the climate device, for climate devices with split target temperatu...
Definition: climate.h:189
const StringRef & get_name() const
Definition: entity_base.cpp:10
std::string climate_json(climate::Climate *obj, JsonDetail start_config)
Dump the climate details.
std::string method
The method that&#39;s being called, for example "turn_on".
Definition: web_server.h:38
void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a date request under &#39;/date/<id>&#39;.
Definition: web_server.cpp:861
Base class for all locks.
Definition: lock.h:103
ClimateAction action
The active state of the climate device.
Definition: climate.h:176
ClimateDevice - This is the base class for all climate integrations.
Definition: climate.h:168
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.
Definition: web_server.cpp:537
bool state
Definition: fan.h:34
std::string cover_json(cover::Cover *obj, JsonDetail start_config)
Dump the cover state as a JSON string.
Definition: web_server.cpp:785
void handle_js_request(AsyncWebServerRequest *request)
Handle included js request under &#39;/0.js&#39;.
Definition: web_server.cpp:391
void set_js_include(const char *js_include)
Set local path to the script that&#39;s embedded in the index page.
Definition: web_server.cpp:110
const LogString * climate_swing_mode_to_string(ClimateSwingMode swing_mode)
Convert the given ClimateSwingMode to a human-readable string.