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