mirror of
https://github.com/esphome/esphome.git
synced 2025-09-09 23:02:23 +01:00
Webserver v2 (#2688)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
@@ -24,11 +24,20 @@
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_WEBSERVER_LOCAL
|
||||
#include "server_index.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server {
|
||||
|
||||
static const char *const TAG = "web_server";
|
||||
|
||||
#if USE_WEBSERVER_VERSION == 1
|
||||
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) {
|
||||
stream->print("<tr class=\"");
|
||||
@@ -49,6 +58,7 @@ void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &
|
||||
stream->print("</td>");
|
||||
stream->print("</tr>");
|
||||
}
|
||||
#endif
|
||||
|
||||
UrlMatch match_url(const std::string &url, bool only_domain = false) {
|
||||
UrlMatch match;
|
||||
@@ -87,76 +97,94 @@ void WebServer::setup() {
|
||||
this->base_->init();
|
||||
|
||||
this->events_.onConnect([this](AsyncEventSourceClient *client) {
|
||||
// Configure reconnect timeout
|
||||
client->send("", "ping", millis(), 30000);
|
||||
// Configure reconnect timeout and send config
|
||||
|
||||
client->send(json::build_json([this](JsonObject root) {
|
||||
root["title"] = App.get_name();
|
||||
root["ota"] = this->allow_ota_;
|
||||
root["lang"] = "en";
|
||||
}).c_str(),
|
||||
"ping", millis(), 30000);
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
for (auto *obj : App.get_sensors()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->sensor_json(obj, obj->state).c_str(), "state");
|
||||
client->send(this->sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
for (auto *obj : App.get_switches()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->switch_json(obj, obj->state).c_str(), "state");
|
||||
client->send(this->switch_json(obj, obj->state, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
for (auto *obj : App.get_buttons())
|
||||
client->send(this->button_json(obj, DETAIL_ALL).c_str(), "state");
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto *obj : App.get_binary_sensors()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state");
|
||||
client->send(this->binary_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
for (auto *obj : App.get_fans()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->fan_json(obj).c_str(), "state");
|
||||
client->send(this->fan_json(obj, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
for (auto *obj : App.get_lights()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->light_json(obj).c_str(), "state");
|
||||
client->send(this->light_json(obj, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
for (auto *obj : App.get_text_sensors()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->text_sensor_json(obj, obj->state).c_str(), "state");
|
||||
client->send(this->text_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
for (auto *obj : App.get_covers()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->cover_json(obj).c_str(), "state");
|
||||
client->send(this->cover_json(obj, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
for (auto *obj : App.get_numbers()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->number_json(obj, obj->state).c_str(), "state");
|
||||
client->send(this->number_json(obj, obj->state, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
for (auto *obj : App.get_selects()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->select_json(obj, obj->state).c_str(), "state");
|
||||
client->send(this->select_json(obj, obj->state, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
for (auto *obj : App.get_climates()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->climate_json(obj, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
for (auto *obj : App.get_locks()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->lock_json(obj, obj->state).c_str(), "state");
|
||||
client->send(this->lock_json(obj, obj->state, DETAIL_ALL).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
});
|
||||
@@ -181,15 +209,27 @@ void WebServer::dump_config() {
|
||||
}
|
||||
float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
|
||||
|
||||
#ifdef USE_WEBSERVER_LOCAL
|
||||
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
}
|
||||
#else
|
||||
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *stream = request->beginResponseStream("text/html");
|
||||
std::string title = App.get_name() + " Web Server";
|
||||
stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8>"
|
||||
"<meta name=\"viewport\" content=\"width=device-width, "
|
||||
"initial-scale=1.0\"><title>"));
|
||||
// All content is controlled and created by user - so allowing all origins is fine here.
|
||||
stream->addHeader("Access-Control-Allow-Origin", "*");
|
||||
#if USE_WEBSERVER_VERSION == 1
|
||||
const std::string &title = App.get_name();
|
||||
stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta "
|
||||
"name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>"));
|
||||
stream->print(title.c_str());
|
||||
stream->print(F("</title>"));
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#else
|
||||
stream->print(F("<!DOCTYPE html><html><head><meta charset=UTF-8><link rel=icon href=data:>"));
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">"));
|
||||
#endif
|
||||
if (strlen(this->css_url_) > 0) {
|
||||
@@ -197,11 +237,12 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
stream->print(this->css_url_);
|
||||
stream->print(F("\">"));
|
||||
}
|
||||
stream->print(F("</head><body><article class=\"markdown-body\"><h1>"));
|
||||
stream->print(F("</head><body>"));
|
||||
#if USE_WEBSERVER_VERSION == 1
|
||||
stream->print(F("<article class=\"markdown-body\"><h1>"));
|
||||
stream->print(title.c_str());
|
||||
stream->print(F("</h1><h2>States</h2><table id=\"states\"><thead><tr><th>Name<th>State<th>Actions<tbody>"));
|
||||
// All content is controlled and created by user - so allowing all origins is fine here.
|
||||
stream->addHeader("Access-Control-Allow-Origin", "*");
|
||||
stream->print(F("</h1>"));
|
||||
stream->print(F("<h2>States</h2><table id=\"states\"><thead><tr><th>Name<th>State<th>Actions<tbody>"));
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
for (auto *obj : App.get_sensors()) {
|
||||
@@ -308,6 +349,13 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
for (auto *obj : App.get_climates()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
write_row(stream, obj, "climate", "");
|
||||
}
|
||||
#endif
|
||||
|
||||
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
||||
"REST API documentation.</p>"));
|
||||
if (this->allow_ota_) {
|
||||
@@ -316,23 +364,30 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
|
||||
}
|
||||
stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>"));
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
if (this->js_include_ != nullptr) {
|
||||
stream->print(F("<script src=\"/0.js\"></script>"));
|
||||
stream->print(F("<script type=\"module\" src=\"/0.js\"></script>"));
|
||||
}
|
||||
#endif
|
||||
#if USE_WEBSERVER_VERSION == 2
|
||||
stream->print(F("<esp-app></esp-app>"));
|
||||
#endif
|
||||
if (strlen(this->js_url_) > 0) {
|
||||
stream->print(F("<script src=\""));
|
||||
stream->print(this->js_url_);
|
||||
stream->print(F("\"></script>"));
|
||||
}
|
||||
#if USE_WEBSERVER_VERSION == 1
|
||||
stream->print(F("</article></body></html>"));
|
||||
#else
|
||||
stream->print(F("</body></html>"));
|
||||
#endif
|
||||
|
||||
request->send(stream);
|
||||
}
|
||||
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
void WebServer::handle_css_request(AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *stream = request->beginResponseStream("text/css");
|
||||
if (this->css_include_ != nullptr) {
|
||||
@@ -343,10 +398,11 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
void WebServer::handle_js_request(AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *stream = request->beginResponseStream("text/javascript");
|
||||
if (this->js_include_ != nullptr) {
|
||||
stream->addHeader("Access-Control-Allow-Origin", "*");
|
||||
stream->print(this->js_include_);
|
||||
}
|
||||
|
||||
@@ -354,64 +410,75 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#define set_json_id(root, obj, sensor, start_config) \
|
||||
(root)["id"] = sensor; \
|
||||
if (((start_config) == DETAIL_ALL)) \
|
||||
(root)["name"] = (obj)->get_name();
|
||||
|
||||
#define set_json_value(root, obj, sensor, value, start_config) \
|
||||
set_json_id((root), (obj), sensor, start_config)(root)["value"] = value;
|
||||
|
||||
#define set_json_state_value(root, obj, sensor, state, value, start_config) \
|
||||
set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state;
|
||||
|
||||
#define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \
|
||||
set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; \
|
||||
if (((start_config) == DETAIL_ALL)) \
|
||||
(root)["icon"] = (obj)->get_icon();
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
this->events_.send(this->sensor_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (sensor::Sensor *obj : App.get_sensors()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
std::string data = this->sensor_json(obj, obj->state);
|
||||
std::string data = this->sensor_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::sensor_json(sensor::Sensor *obj, float value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "sensor-" + obj->get_object_id();
|
||||
std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
|
||||
if (!obj->get_unit_of_measurement().empty())
|
||||
state += " " + obj->get_unit_of_measurement();
|
||||
root["state"] = state;
|
||||
root["value"] = value;
|
||||
set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
|
||||
this->events_.send(this->text_sensor_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (text_sensor::TextSensor *obj : App.get_text_sensors()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
std::string data = this->text_sensor_json(obj, obj->state);
|
||||
std::string data = this->text_sensor_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "text_sensor-" + obj->get_object_id();
|
||||
root["state"] = value;
|
||||
root["value"] = value;
|
||||
std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value,
|
||||
JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
this->events_.send(this->switch_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
std::string WebServer::switch_json(switch_::Switch *obj, bool value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "switch-" + obj->get_object_id();
|
||||
root["state"] = value ? "ON" : "OFF";
|
||||
root["value"] = value;
|
||||
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
|
||||
});
|
||||
}
|
||||
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -420,7 +487,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->switch_json(obj, obj->state);
|
||||
std::string data = this->switch_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "toggle") {
|
||||
this->defer([obj]() { obj->toggle(); });
|
||||
@@ -441,14 +508,19 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) {
|
||||
return json::build_json(
|
||||
[obj, start_config](JsonObject root) { set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); });
|
||||
}
|
||||
|
||||
void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (button::Button *obj : App.get_buttons()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_POST && match.method == "press") {
|
||||
this->defer([obj]() { obj->press(); });
|
||||
request->send(200);
|
||||
return;
|
||||
} else {
|
||||
request->send(404);
|
||||
}
|
||||
@@ -460,20 +532,18 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "binary_sensor-" + obj->get_object_id();
|
||||
root["state"] = value ? "ON" : "OFF";
|
||||
root["value"] = value;
|
||||
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
|
||||
});
|
||||
}
|
||||
void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
std::string data = this->binary_sensor_json(obj, obj->state);
|
||||
std::string data = this->binary_sensor_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
@@ -482,15 +552,15 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); }
|
||||
std::string WebServer::fan_json(fan::Fan *obj) {
|
||||
return json::build_json([obj](JsonObject root) {
|
||||
root["id"] = "fan-" + obj->get_object_id();
|
||||
root["state"] = obj->state ? "ON" : "OFF";
|
||||
root["value"] = obj->state;
|
||||
void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); }
|
||||
std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, start_config);
|
||||
const auto traits = obj->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
root["speed_level"] = obj->speed;
|
||||
root["speed_count"] = traits.supported_speed_count();
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
@@ -517,7 +587,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->fan_json(obj);
|
||||
std::string data = this->fan_json(obj, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "toggle") {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
@@ -573,14 +643,16 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void WebServer::on_light_update(light::LightState *obj) { this->events_.send(this->light_json(obj).c_str(), "state"); }
|
||||
void WebServer::on_light_update(light::LightState *obj) {
|
||||
this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (light::LightState *obj : App.get_lights()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->light_json(obj);
|
||||
std::string data = this->light_json(obj, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "toggle") {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
@@ -632,24 +704,34 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::light_json(light::LightState *obj) {
|
||||
return json::build_json([obj](JsonObject root) {
|
||||
root["id"] = "light-" + obj->get_object_id();
|
||||
std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_id(root, obj, "light-" + obj->get_object_id(), start_config);
|
||||
root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
|
||||
|
||||
light::LightJSONSchema::dump_json(*obj, root);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
JsonArray opt = root.createNestedArray("effects");
|
||||
opt.add("None");
|
||||
for (auto const &option : obj->get_effects()) {
|
||||
opt.add(option->get_name());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
void WebServer::on_cover_update(cover::Cover *obj) { this->events_.send(this->cover_json(obj).c_str(), "state"); }
|
||||
void WebServer::on_cover_update(cover::Cover *obj) {
|
||||
this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (cover::Cover *obj : App.get_covers()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->cover_json(obj);
|
||||
std::string data = this->cover_json(obj, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
continue;
|
||||
}
|
||||
@@ -684,11 +766,10 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::cover_json(cover::Cover *obj) {
|
||||
return json::build_json([obj](JsonObject root) {
|
||||
root["id"] = "cover-" + obj->get_object_id();
|
||||
root["state"] = obj->is_fully_closed() ? "CLOSED" : "OPEN";
|
||||
root["value"] = obj->position;
|
||||
std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
|
||||
obj->position, start_config);
|
||||
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
|
||||
|
||||
if (obj->get_traits().get_supports_tilt())
|
||||
@@ -699,7 +780,7 @@ std::string WebServer::cover_json(cover::Cover *obj) {
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void WebServer::on_number_update(number::Number *obj, float state) {
|
||||
this->events_.send(this->number_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (auto *obj : App.get_numbers()) {
|
||||
@@ -707,18 +788,16 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->number_json(obj, obj->state);
|
||||
std::string data = this->number_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.method != "set") {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
auto call = obj->make_call();
|
||||
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
@@ -732,19 +811,30 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::number_json(number::Number *obj, float value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "number-" + obj->get_object_id();
|
||||
|
||||
std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_id(root, obj, "number-" + obj->get_object_id(), start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root["min_value"] = obj->traits.get_min_value();
|
||||
root["max_value"] = obj->traits.get_max_value();
|
||||
root["step"] = obj->traits.get_step();
|
||||
root["mode"] = (int) obj->traits.get_mode();
|
||||
}
|
||||
std::string state = str_sprintf("%f", value);
|
||||
root["state"] = state;
|
||||
root["value"] = value;
|
||||
if (isnan(value)) {
|
||||
root["value"] = "\"NaN\"";
|
||||
} else {
|
||||
root["value"] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void WebServer::on_select_update(select::Select *obj, const std::string &state) {
|
||||
this->events_.send(this->select_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (auto *obj : App.get_selects()) {
|
||||
@@ -752,7 +842,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->select_json(obj, obj->state);
|
||||
std::string data = this->select_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
@@ -775,24 +865,158 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::select_json(select::Select *obj, const std::string &value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "select-" + obj->get_object_id();
|
||||
root["state"] = value;
|
||||
root["value"] = value;
|
||||
std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
JsonArray opt = root.createNestedArray("option");
|
||||
for (auto &option : obj->traits.get_options()) {
|
||||
opt.add(option);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void WebServer::on_climate_update(climate::Climate *obj) {
|
||||
this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
|
||||
void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (auto *obj : App.get_climates()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->climate_json(obj, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.method != "set") {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
auto call = obj->make_call();
|
||||
|
||||
if (request->hasParam("mode")) {
|
||||
String mode = request->getParam("mode")->value();
|
||||
call.set_mode(mode.c_str());
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature_high")) {
|
||||
String value = request->getParam("target_temperature_high")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature_high(*value_f);
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature_low")) {
|
||||
String value = request->getParam("target_temperature_low")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature_low(*value_f);
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature")) {
|
||||
String value = request->getParam("target_temperature")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature(*value_f);
|
||||
}
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
|
||||
// Longest: HORIZONTAL
|
||||
#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P)((mode_s)), 15)
|
||||
|
||||
std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
|
||||
const auto traits = obj->get_traits();
|
||||
char __buf[16];
|
||||
|
||||
if (start_config == DETAIL_ALL) {
|
||||
JsonArray opt = root.createNestedArray("modes");
|
||||
for (climate::ClimateMode m : traits.get_supported_modes())
|
||||
opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m)));
|
||||
if (!traits.get_supported_custom_fan_modes().empty()) {
|
||||
JsonArray opt = root.createNestedArray("fan_modes");
|
||||
for (climate::ClimateFanMode m : traits.get_supported_fan_modes())
|
||||
opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m)));
|
||||
}
|
||||
|
||||
if (!traits.get_supported_custom_fan_modes().empty()) {
|
||||
JsonArray opt = root.createNestedArray("custom_fan_modes");
|
||||
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
|
||||
opt.add(custom_fan_mode);
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
JsonArray opt = root.createNestedArray("swing_modes");
|
||||
for (auto swing_mode : traits.get_supported_swing_modes())
|
||||
opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode)));
|
||||
}
|
||||
if (traits.get_supports_presets() && obj->preset.has_value()) {
|
||||
JsonArray opt = root.createNestedArray("presets");
|
||||
for (climate::ClimatePreset m : traits.get_supported_presets())
|
||||
opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m)));
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
|
||||
JsonArray opt = root.createNestedArray("custom_presets");
|
||||
for (auto const &custom_preset : traits.get_supported_custom_presets())
|
||||
opt.add(custom_preset);
|
||||
}
|
||||
}
|
||||
|
||||
root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode));
|
||||
|
||||
if (traits.get_supports_action()) {
|
||||
root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action));
|
||||
}
|
||||
if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) {
|
||||
root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value()));
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && obj->custom_fan_mode.has_value()) {
|
||||
root["custom_fan_mode"] = obj->custom_fan_mode.value().c_str();
|
||||
}
|
||||
if (traits.get_supports_presets() && obj->preset.has_value()) {
|
||||
root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value()));
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
|
||||
root["custom_preset"] = obj->custom_preset.value().c_str();
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode));
|
||||
}
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
root["current_temperature"] = obj->current_temperature;
|
||||
}
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
root["current_temperature_low"] = obj->target_temperature_low;
|
||||
root["current_temperature_high"] = obj->target_temperature_low;
|
||||
} else {
|
||||
root["target_temperature"] = obj->target_temperature;
|
||||
root["state"] = obj->target_temperature;
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||
this->events_.send(this->lock_json(obj, obj->state).c_str(), "state");
|
||||
this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "lock-" + obj->get_object_id();
|
||||
root["state"] = lock::lock_state_to_string(value);
|
||||
root["value"] = value;
|
||||
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value,
|
||||
start_config);
|
||||
});
|
||||
}
|
||||
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -801,7 +1025,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->lock_json(obj, obj->state);
|
||||
std::string data = this->lock_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "lock") {
|
||||
this->defer([obj]() { obj->lock(); });
|
||||
@@ -825,12 +1049,12 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||
if (request->url() == "/")
|
||||
return true;
|
||||
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
if (request->url() == "/0.css")
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
if (request->url() == "/0.js")
|
||||
return true;
|
||||
#endif
|
||||
@@ -888,6 +1112,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate")
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
|
||||
return true;
|
||||
@@ -901,14 +1130,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
if (request->url() == "/0.css") {
|
||||
this->handle_css_request(request);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
if (request->url() == "/0.js") {
|
||||
this->handle_js_request(request);
|
||||
return;
|
||||
@@ -986,9 +1215,17 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
if (match.domain == "climate") {
|
||||
this->handle_climate_request(request, match);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if (match.domain == "lock") {
|
||||
this->handle_lock_request(request, match);
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user