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