ESPHome  2024.7.2
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 #if USE_WEBSERVER_VERSION == 2
31 #include "server_index_v2.h"
32 #elif USE_WEBSERVER_VERSION == 3
33 #include "server_index_v3.h"
34 #endif
35 #endif
36 
37 namespace esphome {
38 namespace web_server {
39 
40 static const char *const TAG = "web_server";
41 
42 #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
43 static const char *const HEADER_PNA_NAME = "Private-Network-Access-Name";
44 static const char *const HEADER_PNA_ID = "Private-Network-Access-ID";
45 static const char *const HEADER_CORS_REQ_PNA = "Access-Control-Request-Private-Network";
46 static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-Network";
47 #endif
48 
49 #if USE_WEBSERVER_VERSION == 1
50 void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action,
51  const std::function<void(AsyncResponseStream &stream, EntityBase *obj)> &action_func = nullptr) {
52  stream->print("<tr class=\"");
53  stream->print(klass.c_str());
54  if (obj->is_internal())
55  stream->print(" internal");
56  stream->print("\" id=\"");
57  stream->print(klass.c_str());
58  stream->print("-");
59  stream->print(obj->get_object_id().c_str());
60  stream->print("\"><td>");
61  stream->print(obj->get_name().c_str());
62  stream->print("</td><td></td><td>");
63  stream->print(action.c_str());
64  if (action_func) {
65  action_func(*stream, obj);
66  }
67  stream->print("</td>");
68  stream->print("</tr>");
69 }
70 #endif
71 
72 UrlMatch match_url(const std::string &url, bool only_domain = false) {
73  UrlMatch match;
74  match.valid = false;
75  size_t domain_end = url.find('/', 1);
76  if (domain_end == std::string::npos)
77  return match;
78  match.domain = url.substr(1, domain_end - 1);
79  if (only_domain) {
80  match.valid = true;
81  return match;
82  }
83  if (url.length() == domain_end - 1)
84  return match;
85  size_t id_begin = domain_end + 1;
86  size_t id_end = url.find('/', id_begin);
87  match.valid = true;
88  if (id_end == std::string::npos) {
89  match.id = url.substr(id_begin, url.length() - id_begin);
90  return match;
91  }
92  match.id = url.substr(id_begin, id_end - id_begin);
93  size_t method_begin = id_end + 1;
94  match.method = url.substr(method_begin, url.length() - method_begin);
95  return match;
96 }
97 
99  : base_(base), entities_iterator_(ListEntitiesIterator(this)) {
100 #ifdef USE_ESP32
101  to_schedule_lock_ = xSemaphoreCreateMutex();
102 #endif
103 }
104 
105 #if USE_WEBSERVER_VERSION == 1
106 void WebServer::set_css_url(const char *css_url) { this->css_url_ = css_url; }
107 void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; }
108 #endif
109 
110 #ifdef USE_WEBSERVER_CSS_INCLUDE
111 void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; }
112 #endif
113 #ifdef USE_WEBSERVER_JS_INCLUDE
114 void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; }
115 #endif
116 
118  return json::build_json([this](JsonObject root) {
119  root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name();
120  root["comment"] = App.get_comment();
121  root["ota"] = this->allow_ota_;
122  root["log"] = this->expose_log_;
123  root["lang"] = "en";
124  });
125 }
126 
128  ESP_LOGCONFIG(TAG, "Setting up web server...");
129  this->setup_controller(this->include_internal_);
130  this->base_->init();
131 
132  this->events_.onConnect([this](AsyncEventSourceClient *client) {
133  // Configure reconnect timeout and send config
134  client->send(this->get_config_json().c_str(), "ping", millis(), 30000);
135 
137  });
138 
139 #ifdef USE_LOGGER
140  if (logger::global_logger != nullptr && this->expose_log_) {
142  [this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); });
143  }
144 #endif
145  this->base_->add_handler(&this->events_);
146  this->base_->add_handler(this);
147 
148  if (this->allow_ota_)
149  this->base_->add_ota_handler();
150 
151  this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); });
152 }
154 #ifdef USE_ESP32
155  if (xSemaphoreTake(this->to_schedule_lock_, 0L)) {
156  std::function<void()> fn;
157  if (!to_schedule_.empty()) {
158  // scheduler execute things out of order which may lead to incorrect state
159  // this->defer(std::move(to_schedule_.front()));
160  // let's execute it directly from the loop
161  fn = std::move(to_schedule_.front());
162  to_schedule_.pop_front();
163  }
164  xSemaphoreGive(this->to_schedule_lock_);
165  if (fn) {
166  fn();
167  }
168  }
169 #endif
170  this->entities_iterator_.advance();
171 }
173  ESP_LOGCONFIG(TAG, "Web Server:");
174  ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port());
175 }
176 float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
177 
178 #ifdef USE_WEBSERVER_LOCAL
179 void WebServer::handle_index_request(AsyncWebServerRequest *request) {
180  AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
181  response->addHeader("Content-Encoding", "gzip");
182  request->send(response);
183 }
184 #elif USE_WEBSERVER_VERSION == 1
185 void WebServer::handle_index_request(AsyncWebServerRequest *request) {
186  AsyncResponseStream *stream = request->beginResponseStream("text/html");
187  const std::string &title = App.get_name();
188  stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta "
189  "name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>"));
190  stream->print(title.c_str());
191  stream->print(F("</title>"));
192 #ifdef USE_WEBSERVER_CSS_INCLUDE
193  stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">"));
194 #endif
195  if (strlen(this->css_url_) > 0) {
196  stream->print(F(R"(<link rel="stylesheet" href=")"));
197  stream->print(this->css_url_);
198  stream->print(F("\">"));
199  }
200  stream->print(F("</head><body>"));
201  stream->print(F("<article class=\"markdown-body\"><h1>"));
202  stream->print(title.c_str());
203  stream->print(F("</h1>"));
204  stream->print(F("<h2>States</h2><table id=\"states\"><thead><tr><th>Name<th>State<th>Actions<tbody>"));
205 
206 #ifdef USE_SENSOR
207  for (auto *obj : App.get_sensors()) {
208  if (this->include_internal_ || !obj->is_internal())
209  write_row(stream, obj, "sensor", "");
210  }
211 #endif
212 
213 #ifdef USE_SWITCH
214  for (auto *obj : App.get_switches()) {
215  if (this->include_internal_ || !obj->is_internal())
216  write_row(stream, obj, "switch", "<button>Toggle</button>");
217  }
218 #endif
219 
220 #ifdef USE_BUTTON
221  for (auto *obj : App.get_buttons())
222  write_row(stream, obj, "button", "<button>Press</button>");
223 #endif
224 
225 #ifdef USE_BINARY_SENSOR
226  for (auto *obj : App.get_binary_sensors()) {
227  if (this->include_internal_ || !obj->is_internal())
228  write_row(stream, obj, "binary_sensor", "");
229  }
230 #endif
231 
232 #ifdef USE_FAN
233  for (auto *obj : App.get_fans()) {
234  if (this->include_internal_ || !obj->is_internal())
235  write_row(stream, obj, "fan", "<button>Toggle</button>");
236  }
237 #endif
238 
239 #ifdef USE_LIGHT
240  for (auto *obj : App.get_lights()) {
241  if (this->include_internal_ || !obj->is_internal())
242  write_row(stream, obj, "light", "<button>Toggle</button>");
243  }
244 #endif
245 
246 #ifdef USE_TEXT_SENSOR
247  for (auto *obj : App.get_text_sensors()) {
248  if (this->include_internal_ || !obj->is_internal())
249  write_row(stream, obj, "text_sensor", "");
250  }
251 #endif
252 
253 #ifdef USE_COVER
254  for (auto *obj : App.get_covers()) {
255  if (this->include_internal_ || !obj->is_internal())
256  write_row(stream, obj, "cover", "<button>Open</button><button>Close</button>");
257  }
258 #endif
259 
260 #ifdef USE_NUMBER
261  for (auto *obj : App.get_numbers()) {
262  if (this->include_internal_ || !obj->is_internal()) {
263  write_row(stream, obj, "number", "", [](AsyncResponseStream &stream, EntityBase *obj) {
264  number::Number *number = (number::Number *) obj;
265  stream.print(R"(<input type="number" min=")");
266  stream.print(number->traits.get_min_value());
267  stream.print(R"(" max=")");
268  stream.print(number->traits.get_max_value());
269  stream.print(R"(" step=")");
270  stream.print(number->traits.get_step());
271  stream.print(R"(" value=")");
272  stream.print(number->state);
273  stream.print(R"("/>)");
274  });
275  }
276  }
277 #endif
278 
279 #ifdef USE_TEXT
280  for (auto *obj : App.get_texts()) {
281  if (this->include_internal_ || !obj->is_internal()) {
282  write_row(stream, obj, "text", "", [](AsyncResponseStream &stream, EntityBase *obj) {
283  text::Text *text = (text::Text *) obj;
284  auto mode = (int) text->traits.get_mode();
285  stream.print(R"(<input type=")");
286  if (mode == 2) {
287  stream.print(R"(password)");
288  } else { // default
289  stream.print(R"(text)");
290  }
291  stream.print(R"(" minlength=")");
292  stream.print(text->traits.get_min_length());
293  stream.print(R"(" maxlength=")");
294  stream.print(text->traits.get_max_length());
295  stream.print(R"(" pattern=")");
296  stream.print(text->traits.get_pattern().c_str());
297  stream.print(R"(" value=")");
298  stream.print(text->state.c_str());
299  stream.print(R"("/>)");
300  });
301  }
302  }
303 #endif
304 
305 #ifdef USE_SELECT
306  for (auto *obj : App.get_selects()) {
307  if (this->include_internal_ || !obj->is_internal()) {
308  write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) {
309  select::Select *select = (select::Select *) obj;
310  stream.print("<select>");
311  stream.print("<option></option>");
312  for (auto const &option : select->traits.get_options()) {
313  stream.print("<option>");
314  stream.print(option.c_str());
315  stream.print("</option>");
316  }
317  stream.print("</select>");
318  });
319  }
320  }
321 #endif
322 
323 #ifdef USE_LOCK
324  for (auto *obj : App.get_locks()) {
325  if (this->include_internal_ || !obj->is_internal()) {
326  write_row(stream, obj, "lock", "", [](AsyncResponseStream &stream, EntityBase *obj) {
327  lock::Lock *lock = (lock::Lock *) obj;
328  stream.print("<button>Lock</button><button>Unlock</button>");
329  if (lock->traits.get_supports_open()) {
330  stream.print("<button>Open</button>");
331  }
332  });
333  }
334  }
335 #endif
336 
337 #ifdef USE_CLIMATE
338  for (auto *obj : App.get_climates()) {
339  if (this->include_internal_ || !obj->is_internal())
340  write_row(stream, obj, "climate", "");
341  }
342 #endif
343 
344  stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
345  "REST API documentation.</p>"));
346  if (this->allow_ota_) {
347  stream->print(
348  F("<h2>OTA Update</h2><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
349  "type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
350  }
351  stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>"));
352 #ifdef USE_WEBSERVER_JS_INCLUDE
353  if (this->js_include_ != nullptr) {
354  stream->print(F("<script type=\"module\" src=\"/0.js\"></script>"));
355  }
356 #endif
357  if (strlen(this->js_url_) > 0) {
358  stream->print(F("<script src=\""));
359  stream->print(this->js_url_);
360  stream->print(F("\"></script>"));
361  }
362  stream->print(F("</article></body></html>"));
363  request->send(stream);
364 }
365 #elif USE_WEBSERVER_VERSION >= 2
366 void WebServer::handle_index_request(AsyncWebServerRequest *request) {
367  AsyncWebServerResponse *response =
368  request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE);
369  // No gzip header here because the HTML file is so small
370  request->send(response);
371 }
372 #endif
373 
374 #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
375 void WebServer::handle_pna_cors_request(AsyncWebServerRequest *request) {
376  AsyncWebServerResponse *response = request->beginResponse(200, "");
377  response->addHeader(HEADER_CORS_ALLOW_PNA, "true");
378  response->addHeader(HEADER_PNA_NAME, App.get_name().c_str());
379  std::string mac = get_mac_address_pretty();
380  response->addHeader(HEADER_PNA_ID, mac.c_str());
381  request->send(response);
382 }
383 #endif
384 
385 #ifdef USE_WEBSERVER_CSS_INCLUDE
386 void WebServer::handle_css_request(AsyncWebServerRequest *request) {
387  AsyncWebServerResponse *response =
388  request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE);
389  response->addHeader("Content-Encoding", "gzip");
390  request->send(response);
391 }
392 #endif
393 
394 #ifdef USE_WEBSERVER_JS_INCLUDE
395 void WebServer::handle_js_request(AsyncWebServerRequest *request) {
396  AsyncWebServerResponse *response =
397  request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE);
398  response->addHeader("Content-Encoding", "gzip");
399  request->send(response);
400 }
401 #endif
402 
403 #define set_json_id(root, obj, sensor, start_config) \
404  (root)["id"] = sensor; \
405  if (((start_config) == DETAIL_ALL)) { \
406  (root)["name"] = (obj)->get_name(); \
407  (root)["icon"] = (obj)->get_icon(); \
408  (root)["entity_category"] = (obj)->get_entity_category(); \
409  if ((obj)->is_disabled_by_default()) \
410  (root)["is_disabled_by_default"] = (obj)->is_disabled_by_default(); \
411  }
412 
413 #define set_json_value(root, obj, sensor, value, start_config) \
414  set_json_id((root), (obj), sensor, start_config); \
415  (root)["value"] = value;
416 
417 #define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \
418  set_json_value(root, obj, sensor, value, start_config); \
419  (root)["state"] = state;
420 
421 #ifdef USE_SENSOR
423  if (this->events_.count() == 0)
424  return;
425  this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
426 }
427 void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
428  for (sensor::Sensor *obj : App.get_sensors()) {
429  if (obj->get_object_id() != match.id)
430  continue;
431  std::string data = this->sensor_json(obj, obj->state, DETAIL_STATE);
432  request->send(200, "application/json", data.c_str());
433  return;
434  }
435  request->send(404);
436 }
437 std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) {
438  return json::build_json([this, obj, value, start_config](JsonObject root) {
439  std::string state;
440  if (std::isnan(value)) {
441  state = "NA";
442  } else {
443  state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
444  if (!obj->get_unit_of_measurement().empty())
445  state += " " + obj->get_unit_of_measurement();
446  }
447  set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config);
448  if (start_config == DETAIL_ALL) {
449  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
450  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
451  }
452  if (!obj->get_unit_of_measurement().empty())
453  root["uom"] = obj->get_unit_of_measurement();
454  }
455  });
456 }
457 #endif
458 
459 #ifdef USE_TEXT_SENSOR
461  if (this->events_.count() == 0)
462  return;
463  this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
464 }
465 void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
467  if (obj->get_object_id() != match.id)
468  continue;
469  std::string data = this->text_sensor_json(obj, obj->state, DETAIL_STATE);
470  request->send(200, "application/json", data.c_str());
471  return;
472  }
473  request->send(404);
474 }
475 std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value,
476  JsonDetail start_config) {
477  return json::build_json([this, obj, value, start_config](JsonObject root) {
478  set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config);
479  if (start_config == DETAIL_ALL) {
480  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
481  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
482  }
483  }
484  });
485 }
486 #endif
487 
488 #ifdef USE_SWITCH
490  if (this->events_.count() == 0)
491  return;
492  this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state");
493 }
494 void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
495  for (switch_::Switch *obj : App.get_switches()) {
496  if (obj->get_object_id() != match.id)
497  continue;
498 
499  if (request->method() == HTTP_GET && match.method.empty()) {
500  std::string data = this->switch_json(obj, obj->state, DETAIL_STATE);
501  request->send(200, "application/json", data.c_str());
502  } else if (match.method == "toggle") {
503  this->schedule_([obj]() { obj->toggle(); });
504  request->send(200);
505  } else if (match.method == "turn_on") {
506  this->schedule_([obj]() { obj->turn_on(); });
507  request->send(200);
508  } else if (match.method == "turn_off") {
509  this->schedule_([obj]() { obj->turn_off(); });
510  request->send(200);
511  } else {
512  request->send(404);
513  }
514  return;
515  }
516  request->send(404);
517 }
518 std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
519  return json::build_json([this, obj, value, start_config](JsonObject root) {
520  set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
521  if (start_config == DETAIL_ALL) {
522  root["assumed_state"] = obj->assumed_state();
523  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
524  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
525  }
526  }
527  });
528 }
529 #endif
530 
531 #ifdef USE_BUTTON
532 void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
533  for (button::Button *obj : App.get_buttons()) {
534  if (obj->get_object_id() != match.id)
535  continue;
536  if (match.method == "press") {
537  this->schedule_([obj]() { obj->press(); });
538  request->send(200);
539  return;
540  } else {
541  request->send(404);
542  }
543  return;
544  }
545  request->send(404);
546 }
547 std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) {
548  return json::build_json([this, obj, start_config](JsonObject root) {
549  set_json_id(root, obj, "button-" + obj->get_object_id(), start_config);
550  if (start_config == DETAIL_ALL) {
551  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
552  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
553  }
554  }
555  });
556 }
557 #endif
558 
559 #ifdef USE_BINARY_SENSOR
561  if (this->events_.count() == 0)
562  return;
563  this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
564 }
565 void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
567  if (obj->get_object_id() != match.id)
568  continue;
569  std::string data = this->binary_sensor_json(obj, obj->state, DETAIL_STATE);
570  request->send(200, "application/json", data.c_str());
571  return;
572  }
573  request->send(404);
574 }
575 std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
576  return json::build_json([this, obj, value, start_config](JsonObject root) {
577  set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value,
578  start_config);
579  if (start_config == DETAIL_ALL) {
580  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
581  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
582  }
583  }
584  });
585 }
586 #endif
587 
588 #ifdef USE_FAN
590  if (this->events_.count() == 0)
591  return;
592  this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state");
593 }
594 void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
595  for (fan::Fan *obj : App.get_fans()) {
596  if (obj->get_object_id() != match.id)
597  continue;
598 
599  if (request->method() == HTTP_GET && match.method.empty()) {
600  std::string data = this->fan_json(obj, DETAIL_STATE);
601  request->send(200, "application/json", data.c_str());
602  } else if (match.method == "toggle") {
603  this->schedule_([obj]() { obj->toggle().perform(); });
604  request->send(200);
605  } else if (match.method == "turn_on") {
606  auto call = obj->turn_on();
607  if (request->hasParam("speed_level")) {
608  auto speed_level = request->getParam("speed_level")->value();
609  auto val = parse_number<int>(speed_level.c_str());
610  if (!val.has_value()) {
611  ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str());
612  return;
613  }
614  call.set_speed(*val);
615  }
616  if (request->hasParam("oscillation")) {
617  auto speed = request->getParam("oscillation")->value();
618  auto val = parse_on_off(speed.c_str());
619  switch (val) {
620  case PARSE_ON:
621  call.set_oscillating(true);
622  break;
623  case PARSE_OFF:
624  call.set_oscillating(false);
625  break;
626  case PARSE_TOGGLE:
627  call.set_oscillating(!obj->oscillating);
628  break;
629  case PARSE_NONE:
630  request->send(404);
631  return;
632  }
633  }
634  this->schedule_([call]() mutable { call.perform(); });
635  request->send(200);
636  } else if (match.method == "turn_off") {
637  this->schedule_([obj]() { obj->turn_off().perform(); });
638  request->send(200);
639  } else {
640  request->send(404);
641  }
642  return;
643  }
644  request->send(404);
645 }
646 std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
647  return json::build_json([this, obj, start_config](JsonObject root) {
648  set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state,
649  start_config);
650  const auto traits = obj->get_traits();
651  if (traits.supports_speed()) {
652  root["speed_level"] = obj->speed;
653  root["speed_count"] = traits.supported_speed_count();
654  }
655  if (obj->get_traits().supports_oscillation())
656  root["oscillation"] = obj->oscillating;
657  if (start_config == DETAIL_ALL) {
658  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
659  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
660  }
661  }
662  });
663 }
664 #endif
665 
666 #ifdef USE_LIGHT
668  if (this->events_.count() == 0)
669  return;
670  this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state");
671 }
672 void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
673  for (light::LightState *obj : App.get_lights()) {
674  if (obj->get_object_id() != match.id)
675  continue;
676 
677  if (request->method() == HTTP_GET && match.method.empty()) {
678  std::string data = this->light_json(obj, DETAIL_STATE);
679  request->send(200, "application/json", data.c_str());
680  } else if (match.method == "toggle") {
681  this->schedule_([obj]() { obj->toggle().perform(); });
682  request->send(200);
683  } else if (match.method == "turn_on") {
684  auto call = obj->turn_on();
685  if (request->hasParam("brightness")) {
686  auto brightness = parse_number<float>(request->getParam("brightness")->value().c_str());
687  if (brightness.has_value()) {
688  call.set_brightness(*brightness / 255.0f);
689  }
690  }
691  if (request->hasParam("r")) {
692  auto r = parse_number<float>(request->getParam("r")->value().c_str());
693  if (r.has_value()) {
694  call.set_red(*r / 255.0f);
695  }
696  }
697  if (request->hasParam("g")) {
698  auto g = parse_number<float>(request->getParam("g")->value().c_str());
699  if (g.has_value()) {
700  call.set_green(*g / 255.0f);
701  }
702  }
703  if (request->hasParam("b")) {
704  auto b = parse_number<float>(request->getParam("b")->value().c_str());
705  if (b.has_value()) {
706  call.set_blue(*b / 255.0f);
707  }
708  }
709  if (request->hasParam("white_value")) {
710  auto white_value = parse_number<float>(request->getParam("white_value")->value().c_str());
711  if (white_value.has_value()) {
712  call.set_white(*white_value / 255.0f);
713  }
714  }
715  if (request->hasParam("color_temp")) {
716  auto color_temp = parse_number<float>(request->getParam("color_temp")->value().c_str());
717  if (color_temp.has_value()) {
718  call.set_color_temperature(*color_temp);
719  }
720  }
721  if (request->hasParam("flash")) {
722  auto flash = parse_number<uint32_t>(request->getParam("flash")->value().c_str());
723  if (flash.has_value()) {
724  call.set_flash_length(*flash * 1000);
725  }
726  }
727  if (request->hasParam("transition")) {
728  auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
729  if (transition.has_value()) {
730  call.set_transition_length(*transition * 1000);
731  }
732  }
733  if (request->hasParam("effect")) {
734  const char *effect = request->getParam("effect")->value().c_str();
735  call.set_effect(effect);
736  }
737 
738  this->schedule_([call]() mutable { call.perform(); });
739  request->send(200);
740  } else if (match.method == "turn_off") {
741  auto call = obj->turn_off();
742  if (request->hasParam("transition")) {
743  auto transition = parse_number<uint32_t>(request->getParam("transition")->value().c_str());
744  if (transition.has_value()) {
745  call.set_transition_length(*transition * 1000);
746  }
747  }
748  this->schedule_([call]() mutable { call.perform(); });
749  request->send(200);
750  } else {
751  request->send(404);
752  }
753  return;
754  }
755  request->send(404);
756 }
757 std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) {
758  return json::build_json([this, obj, start_config](JsonObject root) {
759  set_json_id(root, obj, "light-" + obj->get_object_id(), start_config);
760  root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
761 
763  if (start_config == DETAIL_ALL) {
764  JsonArray opt = root.createNestedArray("effects");
765  opt.add("None");
766  for (auto const &option : obj->get_effects()) {
767  opt.add(option->get_name());
768  }
769  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
770  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
771  }
772  }
773  });
774 }
775 #endif
776 
777 #ifdef USE_COVER
779  if (this->events_.count() == 0)
780  return;
781  this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state");
782 }
783 void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
784  for (cover::Cover *obj : App.get_covers()) {
785  if (obj->get_object_id() != match.id)
786  continue;
787 
788  if (request->method() == HTTP_GET && match.method.empty()) {
789  std::string data = this->cover_json(obj, DETAIL_STATE);
790  request->send(200, "application/json", data.c_str());
791  continue;
792  }
793 
794  auto call = obj->make_call();
795  if (match.method == "open") {
796  call.set_command_open();
797  } else if (match.method == "close") {
798  call.set_command_close();
799  } else if (match.method == "stop") {
800  call.set_command_stop();
801  } else if (match.method == "toggle") {
802  call.set_command_toggle();
803  } else if (match.method != "set") {
804  request->send(404);
805  return;
806  }
807 
808  auto traits = obj->get_traits();
809  if ((request->hasParam("position") && !traits.get_supports_position()) ||
810  (request->hasParam("tilt") && !traits.get_supports_tilt())) {
811  request->send(409);
812  return;
813  }
814 
815  if (request->hasParam("position")) {
816  auto position = parse_number<float>(request->getParam("position")->value().c_str());
817  if (position.has_value()) {
818  call.set_position(*position);
819  }
820  }
821  if (request->hasParam("tilt")) {
822  auto tilt = parse_number<float>(request->getParam("tilt")->value().c_str());
823  if (tilt.has_value()) {
824  call.set_tilt(*tilt);
825  }
826  }
827 
828  this->schedule_([call]() mutable { call.perform(); });
829  request->send(200);
830  return;
831  }
832  request->send(404);
833 }
834 std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
835  return json::build_json([this, obj, start_config](JsonObject root) {
836  set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
837  obj->position, start_config);
838  root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
839 
840  if (obj->get_traits().get_supports_position())
841  root["position"] = obj->position;
842  if (obj->get_traits().get_supports_tilt())
843  root["tilt"] = obj->tilt;
844  if (start_config == DETAIL_ALL) {
845  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
846  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
847  }
848  }
849  });
850 }
851 #endif
852 
853 #ifdef USE_NUMBER
855  if (this->events_.count() == 0)
856  return;
857  this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state");
858 }
859 void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
860  for (auto *obj : App.get_numbers()) {
861  if (obj->get_object_id() != match.id)
862  continue;
863 
864  if (request->method() == HTTP_GET && match.method.empty()) {
865  std::string data = this->number_json(obj, obj->state, DETAIL_STATE);
866  request->send(200, "application/json", data.c_str());
867  return;
868  }
869  if (match.method != "set") {
870  request->send(404);
871  return;
872  }
873 
874  auto call = obj->make_call();
875  if (request->hasParam("value")) {
876  auto value = parse_number<float>(request->getParam("value")->value().c_str());
877  if (value.has_value())
878  call.set_value(*value);
879  }
880 
881  this->schedule_([call]() mutable { call.perform(); });
882  request->send(200);
883  return;
884  }
885  request->send(404);
886 }
887 
888 std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) {
889  return json::build_json([this, obj, value, start_config](JsonObject root) {
890  set_json_id(root, obj, "number-" + obj->get_object_id(), start_config);
891  if (start_config == DETAIL_ALL) {
892  root["min_value"] =
894  root["max_value"] =
896  root["step"] =
898  root["mode"] = (int) obj->traits.get_mode();
899  if (!obj->traits.get_unit_of_measurement().empty())
900  root["uom"] = obj->traits.get_unit_of_measurement();
901  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
902  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
903  }
904  }
905  if (std::isnan(value)) {
906  root["value"] = "\"NaN\"";
907  root["state"] = "NA";
908  } else {
909  root["value"] = value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step()));
911  if (!obj->traits.get_unit_of_measurement().empty())
912  state += " " + obj->traits.get_unit_of_measurement();
913  root["state"] = state;
914  }
915  });
916 }
917 #endif
918 
919 #ifdef USE_DATETIME_DATE
921  if (this->events_.count() == 0)
922  return;
923  this->events_.send(this->date_json(obj, DETAIL_STATE).c_str(), "state");
924 }
925 void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
926  for (auto *obj : App.get_dates()) {
927  if (obj->get_object_id() != match.id)
928  continue;
929  if (request->method() == HTTP_GET) {
930  std::string data = this->date_json(obj, DETAIL_STATE);
931  request->send(200, "application/json", data.c_str());
932  return;
933  }
934  if (match.method != "set") {
935  request->send(404);
936  return;
937  }
938 
939  auto call = obj->make_call();
940 
941  if (!request->hasParam("value")) {
942  request->send(409);
943  return;
944  }
945 
946  if (request->hasParam("value")) {
947  std::string value = request->getParam("value")->value().c_str();
948  call.set_date(value);
949  }
950 
951  this->schedule_([call]() mutable { call.perform(); });
952  request->send(200);
953  return;
954  }
955  request->send(404);
956 }
957 
958 std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) {
959  return json::build_json([this, obj, start_config](JsonObject root) {
960  set_json_id(root, obj, "date-" + obj->get_object_id(), start_config);
961  std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day);
962  root["value"] = value;
963  root["state"] = value;
964  if (start_config == DETAIL_ALL) {
965  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
966  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
967  }
968  }
969  });
970 }
971 #endif // USE_DATETIME_DATE
972 
973 #ifdef USE_DATETIME_TIME
975  if (this->events_.count() == 0)
976  return;
977  this->events_.send(this->time_json(obj, DETAIL_STATE).c_str(), "state");
978 }
979 void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
980  for (auto *obj : App.get_times()) {
981  if (obj->get_object_id() != match.id)
982  continue;
983  if (request->method() == HTTP_GET && match.method.empty()) {
984  std::string data = this->time_json(obj, DETAIL_STATE);
985  request->send(200, "application/json", data.c_str());
986  return;
987  }
988  if (match.method != "set") {
989  request->send(404);
990  return;
991  }
992 
993  auto call = obj->make_call();
994 
995  if (!request->hasParam("value")) {
996  request->send(409);
997  return;
998  }
999 
1000  if (request->hasParam("value")) {
1001  std::string value = request->getParam("value")->value().c_str();
1002  call.set_time(value);
1003  }
1004 
1005  this->schedule_([call]() mutable { call.perform(); });
1006  request->send(200);
1007  return;
1008  }
1009  request->send(404);
1010 }
1011 std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) {
1012  return json::build_json([this, obj, start_config](JsonObject root) {
1013  set_json_id(root, obj, "time-" + obj->get_object_id(), start_config);
1014  std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second);
1015  root["value"] = value;
1016  root["state"] = value;
1017  if (start_config == DETAIL_ALL) {
1018  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1019  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1020  }
1021  }
1022  });
1023 }
1024 #endif // USE_DATETIME_TIME
1025 
1026 #ifdef USE_DATETIME_DATETIME
1028  if (this->events_.count() == 0)
1029  return;
1030  this->events_.send(this->datetime_json(obj, DETAIL_STATE).c_str(), "state");
1031 }
1032 void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1033  for (auto *obj : App.get_datetimes()) {
1034  if (obj->get_object_id() != match.id)
1035  continue;
1036  if (request->method() == HTTP_GET && match.method.empty()) {
1037  std::string data = this->datetime_json(obj, DETAIL_STATE);
1038  request->send(200, "application/json", data.c_str());
1039  return;
1040  }
1041  if (match.method != "set") {
1042  request->send(404);
1043  return;
1044  }
1045 
1046  auto call = obj->make_call();
1047 
1048  if (!request->hasParam("value")) {
1049  request->send(409);
1050  return;
1051  }
1052 
1053  if (request->hasParam("value")) {
1054  std::string value = request->getParam("value")->value().c_str();
1055  call.set_datetime(value);
1056  }
1057 
1058  this->schedule_([call]() mutable { call.perform(); });
1059  request->send(200);
1060  return;
1061  }
1062  request->send(404);
1063 }
1065  return json::build_json([this, obj, start_config](JsonObject root) {
1066  set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config);
1067  std::string value = str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour,
1068  obj->minute, obj->second);
1069  root["value"] = value;
1070  root["state"] = value;
1071  if (start_config == DETAIL_ALL) {
1072  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1073  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1074  }
1075  }
1076  });
1077 }
1078 #endif // USE_DATETIME_DATETIME
1079 
1080 #ifdef USE_TEXT
1081 void WebServer::on_text_update(text::Text *obj, const std::string &state) {
1082  if (this->events_.count() == 0)
1083  return;
1084  this->events_.send(this->text_json(obj, state, DETAIL_STATE).c_str(), "state");
1085 }
1086 void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1087  for (auto *obj : App.get_texts()) {
1088  if (obj->get_object_id() != match.id)
1089  continue;
1090 
1091  if (request->method() == HTTP_GET && match.method.empty()) {
1092  std::string data = this->text_json(obj, obj->state, DETAIL_STATE);
1093  request->send(200, "text/json", data.c_str());
1094  return;
1095  }
1096  if (match.method != "set") {
1097  request->send(404);
1098  return;
1099  }
1100 
1101  auto call = obj->make_call();
1102  if (request->hasParam("value")) {
1103  String value = request->getParam("value")->value();
1104  call.set_value(value.c_str());
1105  }
1106 
1107  this->defer([call]() mutable { call.perform(); });
1108  request->send(200);
1109  return;
1110  }
1111  request->send(404);
1112 }
1113 
1114 std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) {
1115  return json::build_json([this, obj, value, start_config](JsonObject root) {
1116  set_json_id(root, obj, "text-" + obj->get_object_id(), start_config);
1117  root["min_length"] = obj->traits.get_min_length();
1118  root["max_length"] = obj->traits.get_max_length();
1119  root["pattern"] = obj->traits.get_pattern();
1121  root["state"] = "********";
1122  } else {
1123  root["state"] = value;
1124  }
1125  root["value"] = value;
1126  if (start_config == DETAIL_ALL) {
1127  root["mode"] = (int) obj->traits.get_mode();
1128  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1129  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1130  }
1131  }
1132  });
1133 }
1134 #endif
1135 
1136 #ifdef USE_SELECT
1137 void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
1138  if (this->events_.count() == 0)
1139  return;
1140  this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
1141 }
1142 void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1143  for (auto *obj : App.get_selects()) {
1144  if (obj->get_object_id() != match.id)
1145  continue;
1146 
1147  if (request->method() == HTTP_GET && match.method.empty()) {
1148  auto detail = DETAIL_STATE;
1149  auto *param = request->getParam("detail");
1150  if (param && param->value() == "all") {
1151  detail = DETAIL_ALL;
1152  }
1153  std::string data = this->select_json(obj, obj->state, detail);
1154  request->send(200, "application/json", data.c_str());
1155  return;
1156  }
1157 
1158  if (match.method != "set") {
1159  request->send(404);
1160  return;
1161  }
1162 
1163  auto call = obj->make_call();
1164 
1165  if (request->hasParam("option")) {
1166  auto option = request->getParam("option")->value();
1167  call.set_option(option.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations)
1168  }
1169 
1170  this->schedule_([call]() mutable { call.perform(); });
1171  request->send(200);
1172  return;
1173  }
1174  request->send(404);
1175 }
1176 std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) {
1177  return json::build_json([this, obj, value, start_config](JsonObject root) {
1178  set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config);
1179  if (start_config == DETAIL_ALL) {
1180  JsonArray opt = root.createNestedArray("option");
1181  for (auto &option : obj->traits.get_options()) {
1182  opt.add(option);
1183  }
1184  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1185  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1186  }
1187  }
1188  });
1189 }
1190 #endif
1191 
1192 // Longest: HORIZONTAL
1193 #define PSTR_LOCAL(mode_s) strncpy_P(buf, (PGM_P) ((mode_s)), 15)
1194 
1195 #ifdef USE_CLIMATE
1197  if (this->events_.count() == 0)
1198  return;
1199  this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
1200 }
1201 void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1202  for (auto *obj : App.get_climates()) {
1203  if (obj->get_object_id() != match.id)
1204  continue;
1205 
1206  if (request->method() == HTTP_GET && match.method.empty()) {
1207  std::string data = this->climate_json(obj, DETAIL_STATE);
1208  request->send(200, "application/json", data.c_str());
1209  return;
1210  }
1211 
1212  if (match.method != "set") {
1213  request->send(404);
1214  return;
1215  }
1216 
1217  auto call = obj->make_call();
1218 
1219  if (request->hasParam("mode")) {
1220  auto mode = request->getParam("mode")->value();
1221  call.set_mode(mode.c_str());
1222  }
1223 
1224  if (request->hasParam("target_temperature_high")) {
1225  auto target_temperature_high = parse_number<float>(request->getParam("target_temperature_high")->value().c_str());
1226  if (target_temperature_high.has_value())
1227  call.set_target_temperature_high(*target_temperature_high);
1228  }
1229 
1230  if (request->hasParam("target_temperature_low")) {
1231  auto target_temperature_low = parse_number<float>(request->getParam("target_temperature_low")->value().c_str());
1232  if (target_temperature_low.has_value())
1233  call.set_target_temperature_low(*target_temperature_low);
1234  }
1235 
1236  if (request->hasParam("target_temperature")) {
1237  auto target_temperature = parse_number<float>(request->getParam("target_temperature")->value().c_str());
1238  if (target_temperature.has_value())
1239  call.set_target_temperature(*target_temperature);
1240  }
1241 
1242  this->schedule_([call]() mutable { call.perform(); });
1243  request->send(200);
1244  return;
1245  }
1246  request->send(404);
1247 }
1248 std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
1249  return json::build_json([this, obj, start_config](JsonObject root) {
1250  set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
1251  const auto traits = obj->get_traits();
1252  int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
1253  int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
1254  char buf[16];
1255 
1256  if (start_config == DETAIL_ALL) {
1257  JsonArray opt = root.createNestedArray("modes");
1258  for (climate::ClimateMode m : traits.get_supported_modes())
1259  opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m)));
1260  if (!traits.get_supported_custom_fan_modes().empty()) {
1261  JsonArray opt = root.createNestedArray("fan_modes");
1262  for (climate::ClimateFanMode m : traits.get_supported_fan_modes())
1263  opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m)));
1264  }
1265 
1266  if (!traits.get_supported_custom_fan_modes().empty()) {
1267  JsonArray opt = root.createNestedArray("custom_fan_modes");
1268  for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
1269  opt.add(custom_fan_mode);
1270  }
1271  if (traits.get_supports_swing_modes()) {
1272  JsonArray opt = root.createNestedArray("swing_modes");
1273  for (auto swing_mode : traits.get_supported_swing_modes())
1274  opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode)));
1275  }
1276  if (traits.get_supports_presets() && obj->preset.has_value()) {
1277  JsonArray opt = root.createNestedArray("presets");
1278  for (climate::ClimatePreset m : traits.get_supported_presets())
1279  opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m)));
1280  }
1281  if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
1282  JsonArray opt = root.createNestedArray("custom_presets");
1283  for (auto const &custom_preset : traits.get_supported_custom_presets())
1284  opt.add(custom_preset);
1285  }
1286  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1287  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1288  }
1289  }
1290 
1291  bool has_state = false;
1292  root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode));
1293  root["max_temp"] = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy);
1294  root["min_temp"] = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy);
1295  root["step"] = traits.get_visual_target_temperature_step();
1296  if (traits.get_supports_action()) {
1297  root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action));
1298  root["state"] = root["action"];
1299  has_state = true;
1300  }
1301  if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) {
1302  root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value()));
1303  }
1304  if (!traits.get_supported_custom_fan_modes().empty() && obj->custom_fan_mode.has_value()) {
1305  root["custom_fan_mode"] = obj->custom_fan_mode.value().c_str();
1306  }
1307  if (traits.get_supports_presets() && obj->preset.has_value()) {
1308  root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value()));
1309  }
1310  if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
1311  root["custom_preset"] = obj->custom_preset.value().c_str();
1312  }
1313  if (traits.get_supports_swing_modes()) {
1314  root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode));
1315  }
1316  if (traits.get_supports_current_temperature()) {
1317  if (!std::isnan(obj->current_temperature)) {
1318  root["current_temperature"] = value_accuracy_to_string(obj->current_temperature, current_accuracy);
1319  } else {
1320  root["current_temperature"] = "NA";
1321  }
1322  }
1323  if (traits.get_supports_two_point_target_temperature()) {
1324  root["target_temperature_low"] = value_accuracy_to_string(obj->target_temperature_low, target_accuracy);
1325  root["target_temperature_high"] = value_accuracy_to_string(obj->target_temperature_high, target_accuracy);
1326  if (!has_state) {
1327  root["state"] = value_accuracy_to_string((obj->target_temperature_high + obj->target_temperature_low) / 2.0f,
1328  target_accuracy);
1329  }
1330  } else {
1331  root["target_temperature"] = value_accuracy_to_string(obj->target_temperature, target_accuracy);
1332  if (!has_state)
1333  root["state"] = root["target_temperature"];
1334  }
1335  });
1336 }
1337 #endif
1338 
1339 #ifdef USE_LOCK
1341  if (this->events_.count() == 0)
1342  return;
1343  this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state");
1344 }
1345 void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1346  for (lock::Lock *obj : App.get_locks()) {
1347  if (obj->get_object_id() != match.id)
1348  continue;
1349 
1350  if (request->method() == HTTP_GET && match.method.empty()) {
1351  std::string data = this->lock_json(obj, obj->state, DETAIL_STATE);
1352  request->send(200, "application/json", data.c_str());
1353  } else if (match.method == "lock") {
1354  this->schedule_([obj]() { obj->lock(); });
1355  request->send(200);
1356  } else if (match.method == "unlock") {
1357  this->schedule_([obj]() { obj->unlock(); });
1358  request->send(200);
1359  } else if (match.method == "open") {
1360  this->schedule_([obj]() { obj->open(); });
1361  request->send(200);
1362  } else {
1363  request->send(404);
1364  }
1365  return;
1366  }
1367  request->send(404);
1368 }
1369 std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
1370  return json::build_json([this, obj, value, start_config](JsonObject root) {
1371  set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value,
1372  start_config);
1373  if (start_config == DETAIL_ALL) {
1374  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1375  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1376  }
1377  }
1378  });
1379 }
1380 #endif
1381 
1382 #ifdef USE_VALVE
1384  if (this->events_.count() == 0)
1385  return;
1386  this->events_.send(this->valve_json(obj, DETAIL_STATE).c_str(), "state");
1387 }
1388 void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1389  for (valve::Valve *obj : App.get_valves()) {
1390  if (obj->get_object_id() != match.id)
1391  continue;
1392 
1393  if (request->method() == HTTP_GET && match.method.empty()) {
1394  std::string data = this->valve_json(obj, DETAIL_STATE);
1395  request->send(200, "application/json", data.c_str());
1396  continue;
1397  }
1398 
1399  auto call = obj->make_call();
1400  if (match.method == "open") {
1401  call.set_command_open();
1402  } else if (match.method == "close") {
1403  call.set_command_close();
1404  } else if (match.method == "stop") {
1405  call.set_command_stop();
1406  } else if (match.method == "toggle") {
1407  call.set_command_toggle();
1408  } else if (match.method != "set") {
1409  request->send(404);
1410  return;
1411  }
1412 
1413  auto traits = obj->get_traits();
1414  if (request->hasParam("position") && !traits.get_supports_position()) {
1415  request->send(409);
1416  return;
1417  }
1418 
1419  if (request->hasParam("position")) {
1420  auto position = parse_number<float>(request->getParam("position")->value().c_str());
1421  if (position.has_value()) {
1422  call.set_position(*position);
1423  }
1424  }
1425 
1426  this->schedule_([call]() mutable { call.perform(); });
1427  request->send(200);
1428  return;
1429  }
1430  request->send(404);
1431 }
1432 std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
1433  return json::build_json([this, obj, start_config](JsonObject root) {
1434  set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
1435  obj->position, start_config);
1436  root["current_operation"] = valve::valve_operation_to_str(obj->current_operation);
1437 
1438  if (obj->get_traits().get_supports_position())
1439  root["position"] = obj->position;
1440  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1441  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1442  }
1443  });
1444 }
1445 #endif
1446 
1447 #ifdef USE_ALARM_CONTROL_PANEL
1449  if (this->events_.count() == 0)
1450  return;
1451  this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state");
1452 }
1453 void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1455  if (obj->get_object_id() != match.id)
1456  continue;
1457 
1458  if (request->method() == HTTP_GET && match.method.empty()) {
1459  std::string data = this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE);
1460  request->send(200, "application/json", data.c_str());
1461  return;
1462  }
1463  }
1464  request->send(404);
1465 }
1468  JsonDetail start_config) {
1469  return json::build_json([this, obj, value, start_config](JsonObject root) {
1470  char buf[16];
1471  set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(),
1472  PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config);
1473  if (start_config == DETAIL_ALL) {
1474  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1475  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1476  }
1477  }
1478  });
1479 }
1480 #endif
1481 
1482 #ifdef USE_EVENT
1483 void WebServer::on_event(event::Event *obj, const std::string &event_type) {
1484  this->events_.send(this->event_json(obj, event_type, DETAIL_STATE).c_str(), "state");
1485 }
1486 
1487 std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) {
1488  return json::build_json([obj, event_type, start_config](JsonObject root) {
1489  set_json_id(root, obj, "event-" + obj->get_object_id(), start_config);
1490  if (!event_type.empty()) {
1491  root["event_type"] = event_type;
1492  }
1493  if (start_config == DETAIL_ALL) {
1494  JsonArray event_types = root.createNestedArray("event_types");
1495  for (auto const &event_type : obj->get_event_types()) {
1496  event_types.add(event_type);
1497  }
1498  root["device_class"] = obj->get_device_class();
1499  }
1500  });
1501 }
1502 #endif
1503 
1504 #ifdef USE_UPDATE
1506  if (this->events_.count() == 0)
1507  return;
1508  this->events_.send(this->update_json(obj, DETAIL_STATE).c_str(), "state");
1509 }
1510 void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
1511  for (update::UpdateEntity *obj : App.get_updates()) {
1512  if (obj->get_object_id() != match.id)
1513  continue;
1514 
1515  if (request->method() == HTTP_GET && match.method.empty()) {
1516  std::string data = this->update_json(obj, DETAIL_STATE);
1517  request->send(200, "application/json", data.c_str());
1518  return;
1519  }
1520 
1521  if (match.method != "install") {
1522  request->send(404);
1523  return;
1524  }
1525 
1526  this->schedule_([obj]() mutable { obj->perform(); });
1527  request->send(200);
1528  return;
1529  }
1530  request->send(404);
1531 }
1532 std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) {
1533  return json::build_json([this, obj, start_config](JsonObject root) {
1534  set_json_id(root, obj, "update-" + obj->get_object_id(), start_config);
1535  root["value"] = obj->update_info.latest_version;
1536  switch (obj->state) {
1538  root["state"] = "NO UPDATE";
1539  break;
1541  root["state"] = "UPDATE AVAILABLE";
1542  break;
1544  root["state"] = "INSTALLING";
1545  break;
1546  default:
1547  root["state"] = "UNKNOWN";
1548  break;
1549  }
1550  if (start_config == DETAIL_ALL) {
1551  root["current_version"] = obj->update_info.current_version;
1552  root["title"] = obj->update_info.title;
1553  root["summary"] = obj->update_info.summary;
1554  root["release_url"] = obj->update_info.release_url;
1555  if (this->sorting_entitys_.find(obj) != this->sorting_entitys_.end()) {
1556  root["sorting_weight"] = this->sorting_entitys_[obj].weight;
1557  }
1558  }
1559  });
1560 }
1561 #endif
1562 
1563 bool WebServer::canHandle(AsyncWebServerRequest *request) {
1564  if (request->url() == "/")
1565  return true;
1566 
1567 #ifdef USE_WEBSERVER_CSS_INCLUDE
1568  if (request->url() == "/0.css")
1569  return true;
1570 #endif
1571 
1572 #ifdef USE_WEBSERVER_JS_INCLUDE
1573  if (request->url() == "/0.js")
1574  return true;
1575 #endif
1576 
1577 #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
1578  if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) {
1579 #ifdef USE_ARDUINO
1580  // Header needs to be added to interesting header list for it to not be
1581  // nuked by the time we handle the request later.
1582  // Only required in Arduino framework.
1583  request->addInterestingHeader(HEADER_CORS_REQ_PNA);
1584 #endif
1585  return true;
1586  }
1587 #endif
1588 
1589  UrlMatch match = match_url(request->url().c_str(), true);
1590  if (!match.valid)
1591  return false;
1592 #ifdef USE_SENSOR
1593  if (request->method() == HTTP_GET && match.domain == "sensor")
1594  return true;
1595 #endif
1596 
1597 #ifdef USE_SWITCH
1598  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "switch")
1599  return true;
1600 #endif
1601 
1602 #ifdef USE_BUTTON
1603  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "button")
1604  return true;
1605 #endif
1606 
1607 #ifdef USE_BINARY_SENSOR
1608  if (request->method() == HTTP_GET && match.domain == "binary_sensor")
1609  return true;
1610 #endif
1611 
1612 #ifdef USE_FAN
1613  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "fan")
1614  return true;
1615 #endif
1616 
1617 #ifdef USE_LIGHT
1618  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "light")
1619  return true;
1620 #endif
1621 
1622 #ifdef USE_TEXT_SENSOR
1623  if (request->method() == HTTP_GET && match.domain == "text_sensor")
1624  return true;
1625 #endif
1626 
1627 #ifdef USE_COVER
1628  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "cover")
1629  return true;
1630 #endif
1631 
1632 #ifdef USE_NUMBER
1633  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "number")
1634  return true;
1635 #endif
1636 
1637 #ifdef USE_DATETIME_DATE
1638  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "date")
1639  return true;
1640 #endif
1641 
1642 #ifdef USE_DATETIME_TIME
1643  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "time")
1644  return true;
1645 #endif
1646 
1647 #ifdef USE_DATETIME_DATETIME
1648  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "datetime")
1649  return true;
1650 #endif
1651 
1652 #ifdef USE_TEXT
1653  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text")
1654  return true;
1655 #endif
1656 
1657 #ifdef USE_SELECT
1658  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "select")
1659  return true;
1660 #endif
1661 
1662 #ifdef USE_CLIMATE
1663  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate")
1664  return true;
1665 #endif
1666 
1667 #ifdef USE_LOCK
1668  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
1669  return true;
1670 #endif
1671 
1672 #ifdef USE_VALVE
1673  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "valve")
1674  return true;
1675 #endif
1676 
1677 #ifdef USE_ALARM_CONTROL_PANEL
1678  if (request->method() == HTTP_GET && match.domain == "alarm_control_panel")
1679  return true;
1680 #endif
1681 
1682 #ifdef USE_UPDATE
1683  if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "update")
1684  return true;
1685 #endif
1686 
1687  return false;
1688 }
1689 void WebServer::handleRequest(AsyncWebServerRequest *request) {
1690  if (request->url() == "/") {
1691  this->handle_index_request(request);
1692  return;
1693  }
1694 
1695 #ifdef USE_WEBSERVER_CSS_INCLUDE
1696  if (request->url() == "/0.css") {
1697  this->handle_css_request(request);
1698  return;
1699  }
1700 #endif
1701 
1702 #ifdef USE_WEBSERVER_JS_INCLUDE
1703  if (request->url() == "/0.js") {
1704  this->handle_js_request(request);
1705  return;
1706  }
1707 #endif
1708 
1709 #ifdef USE_WEBSERVER_PRIVATE_NETWORK_ACCESS
1710  if (request->method() == HTTP_OPTIONS && request->hasHeader(HEADER_CORS_REQ_PNA)) {
1711  this->handle_pna_cors_request(request);
1712  return;
1713  }
1714 #endif
1715 
1716  UrlMatch match = match_url(request->url().c_str());
1717 #ifdef USE_SENSOR
1718  if (match.domain == "sensor") {
1719  this->handle_sensor_request(request, match);
1720  return;
1721  }
1722 #endif
1723 
1724 #ifdef USE_SWITCH
1725  if (match.domain == "switch") {
1726  this->handle_switch_request(request, match);
1727  return;
1728  }
1729 #endif
1730 
1731 #ifdef USE_BUTTON
1732  if (match.domain == "button") {
1733  this->handle_button_request(request, match);
1734  return;
1735  }
1736 #endif
1737 
1738 #ifdef USE_BINARY_SENSOR
1739  if (match.domain == "binary_sensor") {
1740  this->handle_binary_sensor_request(request, match);
1741  return;
1742  }
1743 #endif
1744 
1745 #ifdef USE_FAN
1746  if (match.domain == "fan") {
1747  this->handle_fan_request(request, match);
1748  return;
1749  }
1750 #endif
1751 
1752 #ifdef USE_LIGHT
1753  if (match.domain == "light") {
1754  this->handle_light_request(request, match);
1755  return;
1756  }
1757 #endif
1758 
1759 #ifdef USE_TEXT_SENSOR
1760  if (match.domain == "text_sensor") {
1761  this->handle_text_sensor_request(request, match);
1762  return;
1763  }
1764 #endif
1765 
1766 #ifdef USE_COVER
1767  if (match.domain == "cover") {
1768  this->handle_cover_request(request, match);
1769  return;
1770  }
1771 #endif
1772 
1773 #ifdef USE_NUMBER
1774  if (match.domain == "number") {
1775  this->handle_number_request(request, match);
1776  return;
1777  }
1778 #endif
1779 
1780 #ifdef USE_DATETIME_DATE
1781  if (match.domain == "date") {
1782  this->handle_date_request(request, match);
1783  return;
1784  }
1785 #endif
1786 
1787 #ifdef USE_DATETIME_TIME
1788  if (match.domain == "time") {
1789  this->handle_time_request(request, match);
1790  return;
1791  }
1792 #endif
1793 
1794 #ifdef USE_DATETIME_DATETIME
1795  if (match.domain == "datetime") {
1796  this->handle_datetime_request(request, match);
1797  return;
1798  }
1799 #endif
1800 
1801 #ifdef USE_TEXT
1802  if (match.domain == "text") {
1803  this->handle_text_request(request, match);
1804  return;
1805  }
1806 #endif
1807 
1808 #ifdef USE_SELECT
1809  if (match.domain == "select") {
1810  this->handle_select_request(request, match);
1811  return;
1812  }
1813 #endif
1814 
1815 #ifdef USE_CLIMATE
1816  if (match.domain == "climate") {
1817  this->handle_climate_request(request, match);
1818  return;
1819  }
1820 #endif
1821 
1822 #ifdef USE_LOCK
1823  if (match.domain == "lock") {
1824  this->handle_lock_request(request, match);
1825 
1826  return;
1827  }
1828 #endif
1829 
1830 #ifdef USE_VALVE
1831  if (match.domain == "valve") {
1832  this->handle_valve_request(request, match);
1833  return;
1834  }
1835 #endif
1836 
1837 #ifdef USE_ALARM_CONTROL_PANEL
1838  if (match.domain == "alarm_control_panel") {
1839  this->handle_alarm_control_panel_request(request, match);
1840 
1841  return;
1842  }
1843 #endif
1844 
1845 #ifdef USE_UPDATE
1846  if (match.domain == "update") {
1847  this->handle_update_request(request, match);
1848  return;
1849  }
1850 #endif
1851 }
1852 
1853 bool WebServer::isRequestHandlerTrivial() { return false; }
1854 
1856  this->sorting_entitys_[entity] = SortingComponents{weight};
1857 }
1858 
1859 void WebServer::schedule_(std::function<void()> &&f) {
1860 #ifdef USE_ESP32
1861  xSemaphoreTake(this->to_schedule_lock_, portMAX_DELAY);
1862  to_schedule_.push_back(std::move(f));
1863  xSemaphoreGive(this->to_schedule_lock_);
1864 #else
1865  this->defer(std::move(f));
1866 #endif
1867 }
1868 
1869 } // namespace web_server
1870 } // 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
const std::vector< datetime::DateTimeEntity * > & get_datetimes()
Definition: application.h:355
void handle_pna_cors_request(AsyncWebServerRequest *request)
Definition: web_server.cpp:375
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:859
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:888
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:437
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:178
void on_sensor_update(sensor::Sensor *obj, float state) override
Definition: web_server.cpp:422
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:98
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
void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a time request under &#39;/time/<id>&#39;.
Definition: web_server.cpp:979
const std::vector< climate::Climate * > & get_climates()
Definition: application.h:319
float target_temperature
The target temperature of the climate device.
Definition: climate.h:186
SemaphoreHandle_t to_schedule_lock_
Definition: web_server.h:363
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:565
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.
LockState state
The current reported state of the lock.
Definition: lock.h:122
std::string get_device_class()
Get the device class, using the manual override if set.
Definition: entity_base.cpp:78
const std::vector< update::UpdateEntity * > & get_updates()
Definition: application.h:432
const std::vector< alarm_control_panel::AlarmControlPanel * > & get_alarm_control_panels()
Definition: application.h:410
bool is_fully_closed() const
Helper method to check if the valve is fully closed. Equivalent to comparing .position against 0...
Definition: valve.cpp:166
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;.
TextTraits traits
Definition: text.h:27
const std::vector< valve::Valve * > & get_valves()
Definition: application.h:391
void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a text input request under &#39;/text/<id>&#39;.
CoverOperation current_operation
The current operation of the cover (idle, opening, closing).
Definition: cover.h:116
std::map< EntityBase *, SortingComponents > sorting_entitys_
Definition: web_server.h:347
float position
The position of the valve from 0.0 (fully closed) to 1.0 (fully open).
Definition: valve.h:116
Base class for all buttons.
Definition: button.h:29
void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a update request under &#39;/update/<id>&#39;.
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
std::set< std::string > get_event_types() const
Definition: event.h:28
void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a valve request under &#39;/valve/<id>/<open/close/stop/set>&#39;.
const UpdateState & state
Definition: update_entity.h:38
void defer(const std::string &name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition: component.cpp:130
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:50
const std::string & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
Definition: application.h:205
void setup() override
Setup the internal web server and register handlers.
Definition: web_server.cpp:127
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:292
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override
Definition: web_server.cpp:560
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:672
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:532
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:667
virtual ValveTraits get_traits()=0
void on_event(event::Event *obj, const std::string &event_type) override
float tilt
The current tilt value of the cover from 0.0 to 1.0.
Definition: cover.h:124
const std::vector< datetime::TimeEntity * > & get_times()
Definition: application.h:346
std::string get_object_id() const
Definition: entity_base.cpp:43
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config)
Dump the event details with its value as a JSON string.
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:37
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:427
const std::vector< lock::Lock * > & get_locks()
Definition: application.h:382
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:475
const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE
std::string domain
The domain of the component, for example "sensor".
Definition: web_server.h:38
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.
std::string update_json(update::UpdateEntity *obj, JsonDetail start_config)
Dump the update state with its value as a JSON string.
Logger * global_logger
Definition: logger.cpp:198
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:460
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:111
void on_text_update(text::Text *obj, const std::string &state) override
const std::vector< button::Button * > & get_buttons()
Definition: application.h:265
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:494
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
const UpdateInfo & update_info
Definition: update_entity.h:37
uint8_t custom_preset
Definition: climate.h:579
UrlMatch match_url(const std::string &url, bool only_domain=false)
Definition: web_server.cpp:72
const std::vector< switch_::Switch * > & get_switches()
Definition: application.h:256
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:386
bool valid
Whether this match is valid.
Definition: web_server.h:41
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:181
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
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
std::string time_json(datetime::TimeEntity *obj, JsonDetail start_config)
Dump the time state with its value as a JSON string.
const std::vector< text_sensor::TextSensor * > & get_text_sensors()
Definition: application.h:283
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:274
Application App
Global storage of Application pointer - only one Application can exist.
const std::vector< binary_sensor::BinarySensor * > & get_binary_sensors()
Definition: application.h:247
const std::vector< LightEffect * > & get_effects() const
Get all effects for this light state.
void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a datetime request under &#39;/datetime/<id>&#39;.
bool get_supports_position() const
Definition: valve_traits.h:12
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:518
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:202
void add_entity_to_sorting_list(EntityBase *entity, float weight)
std::string light_json(light::LightState *obj, JsonDetail start_config)
Dump the light state as a JSON string.
Definition: web_server.cpp:757
void on_valve_update(valve::Valve *obj) override
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:364
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:179
std::string valve_json(valve::Valve *obj, JsonDetail start_config)
Dump the valve state as a JSON string.
NumberTraits traits
Definition: number.h:49
void on_time_update(datetime::TimeEntity *obj) override
Definition: web_server.cpp:974
void on_climate_update(climate::Climate *obj) override
const std::vector< cover::Cover * > & get_covers()
Definition: application.h:301
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:107
float get_setup_priority() const override
MQTT setup priority.
Definition: web_server.cpp:176
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:594
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:106
std::string id
The id of the device that&#39;s being accessed, for example "living_room_fan".
Definition: web_server.h:39
const std::vector< light::LightState * > & get_lights()
Definition: application.h:310
void on_date_update(datetime::DateEntity *obj) override
Definition: web_server.cpp:920
std::string get_comment() const
Get the comment of this Application set by pre_setup().
Definition: application.h:211
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:547
void on_cover_update(cover::Cover *obj) override
Definition: web_server.cpp:778
const char * valve_operation_to_str(ValveOperation op)
Definition: valve.cpp:21
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:783
void on_switch_update(switch_::Switch *obj, bool state) override
Definition: web_server.cpp:489
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
void on_datetime_update(datetime::DateTimeEntity *obj) override
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:958
std::string get_config_json()
Return the webserver configuration as JSON.
Definition: web_server.cpp:117
std::string datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config)
Dump the datetime state with its value as a JSON string.
Base-class for all selects.
Definition: select.h:31
void on_fan_update(fan::Fan *obj) override
Definition: web_server.cpp:589
web_server_base::WebServerBase * base_
Definition: web_server.h:344
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
void on_number_update(number::Number *obj, float state) override
Definition: web_server.cpp:854
Base class for all valve devices.
Definition: valve.h:105
std::string fan_json(fan::Fan *obj, JsonDetail start_config)
Dump the fan state as a JSON string.
Definition: web_server.cpp:646
ValveOperation current_operation
The current operation of the valve (idle, opening, closing).
Definition: valve.h:110
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:337
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:373
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:693
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:346
std::deque< std::function< void()> > to_schedule_
Definition: web_server.h:362
const std::vector< number::Number * > & get_numbers()
Definition: application.h:328
const LogString * climate_action_to_string(ClimateAction action)
Convert the given ClimateAction to a human-readable string.
void on_update(update::UpdateEntity *obj) override
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:465
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:40
void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match)
Handle a date request under &#39;/date/<id>&#39;.
Definition: web_server.cpp:925
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:575
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:834
void handle_js_request(AsyncWebServerRequest *request)
Handle included js request under &#39;/0.js&#39;.
Definition: web_server.cpp:395
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:114
const LogString * climate_swing_mode_to_string(ClimateSwingMode swing_mode)
Convert the given ClimateSwingMode to a human-readable string.