1
0
mirror of https://github.com/esphome/esphome.git synced 2025-02-25 14:28:14 +00:00

web_server: ensure fair network sharing + prevent lost state changes via deferred publish at high event load (#7538)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
Nick Kinnan 2025-02-24 18:19:31 -08:00 committed by GitHub
parent c424fea524
commit 5e44a035a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 832 additions and 200 deletions

View File

@ -9,180 +9,184 @@
namespace esphome {
namespace web_server {
ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_(web_server) {}
#ifdef USE_ARDUINO
ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, DeferredUpdateEventSource *es)
: web_server_(ws), events_(es) {}
#endif
#ifdef USE_ESP_IDF
ListEntitiesIterator::ListEntitiesIterator(const WebServer *ws, AsyncEventSource *es) : web_server_(ws), events_(es) {}
#endif
ListEntitiesIterator::~ListEntitiesIterator() {}
#ifdef USE_BINARY_SENSOR
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(
this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::binary_sensor_all_json_generator);
return true;
}
#endif
#ifdef USE_COVER
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_cover(cover::Cover *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::cover_all_json_generator);
return true;
}
#endif
#ifdef USE_FAN
bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_fan(fan::Fan *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::fan_all_json_generator);
return true;
}
#endif
#ifdef USE_LIGHT
bool ListEntitiesIterator::on_light(light::LightState *light) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_light(light::LightState *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::light_all_json_generator);
return true;
}
#endif
#ifdef USE_SENSOR
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_sensor(sensor::Sensor *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::sensor_all_json_generator);
return true;
}
#endif
#ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_switch(switch_::Switch *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(),
"state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::switch_all_json_generator);
return true;
}
#endif
#ifdef USE_BUTTON
bool ListEntitiesIterator::on_button(button::Button *button) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_button(button::Button *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::button_all_json_generator);
return true;
}
#endif
#ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(
this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::text_sensor_all_json_generator);
return true;
}
#endif
#ifdef USE_LOCK
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_lock(lock::Lock *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::lock_all_json_generator);
return true;
}
#endif
#ifdef USE_VALVE
bool ListEntitiesIterator::on_valve(valve::Valve *valve) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_valve(valve::Valve *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->valve_json(valve, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::valve_all_json_generator);
return true;
}
#endif
#ifdef USE_CLIMATE
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_climate(climate::Climate *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::climate_all_json_generator);
return true;
}
#endif
#ifdef USE_NUMBER
bool ListEntitiesIterator::on_number(number::Number *number) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_number(number::Number *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::number_all_json_generator);
return true;
}
#endif
#ifdef USE_DATETIME_DATE
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_date(datetime::DateEntity *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->date_json(date, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::date_all_json_generator);
return true;
}
#endif
#ifdef USE_DATETIME_TIME
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) {
this->web_server_->events_.send(this->web_server_->time_json(time, DETAIL_ALL).c_str(), "state");
bool ListEntitiesIterator::on_time(datetime::TimeEntity *obj) {
if (this->events_->count() == 0)
return true;
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::time_all_json_generator);
return true;
}
#endif
#ifdef USE_DATETIME_DATETIME
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->datetime_json(datetime, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::datetime_all_json_generator);
return true;
}
#endif
#ifdef USE_TEXT
bool ListEntitiesIterator::on_text(text::Text *text) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_text(text::Text *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->text_json(text, text->state, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::text_all_json_generator);
return true;
}
#endif
#ifdef USE_SELECT
bool ListEntitiesIterator::on_select(select::Select *select) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_select(select::Select *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::select_all_json_generator);
return true;
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(
this->web_server_->alarm_control_panel_json(a_alarm_control_panel, a_alarm_control_panel->get_state(), DETAIL_ALL)
.c_str(),
"state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::alarm_control_panel_all_json_generator);
return true;
}
#endif
#ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) {
bool ListEntitiesIterator::on_event(event::Event *obj) {
if (this->events_->count() == 0)
return true;
// Null event type, since we are just iterating over entities
const std::string null_event_type = "";
this->web_server_->events_.send(this->web_server_->event_json(event, null_event_type, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::event_all_json_generator);
return true;
}
#endif
#ifdef USE_UPDATE
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) {
if (this->web_server_->events_.count() == 0)
bool ListEntitiesIterator::on_update(update::UpdateEntity *obj) {
if (this->events_->count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->update_json(update, DETAIL_ALL).c_str(), "state");
this->events_->deferrable_send_state(obj, "state_detail_all", WebServer::update_all_json_generator);
return true;
}
#endif

View File

@ -5,76 +5,97 @@
#include "esphome/core/component.h"
#include "esphome/core/component_iterator.h"
namespace esphome {
#ifdef USE_ESP_IDF
namespace web_server_idf {
class AsyncEventSource;
}
#endif
namespace web_server {
#ifdef USE_ARDUINO
class DeferredUpdateEventSource;
#endif
class WebServer;
class ListEntitiesIterator : public ComponentIterator {
public:
ListEntitiesIterator(WebServer *web_server);
#ifdef USE_ARDUINO
ListEntitiesIterator(const WebServer *ws, DeferredUpdateEventSource *es);
#endif
#ifdef USE_ESP_IDF
ListEntitiesIterator(const WebServer *ws, esphome::web_server_idf::AsyncEventSource *es);
#endif
virtual ~ListEntitiesIterator();
#ifdef USE_BINARY_SENSOR
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
bool on_binary_sensor(binary_sensor::BinarySensor *obj) override;
#endif
#ifdef USE_COVER
bool on_cover(cover::Cover *cover) override;
bool on_cover(cover::Cover *obj) override;
#endif
#ifdef USE_FAN
bool on_fan(fan::Fan *fan) override;
bool on_fan(fan::Fan *obj) override;
#endif
#ifdef USE_LIGHT
bool on_light(light::LightState *light) override;
bool on_light(light::LightState *obj) override;
#endif
#ifdef USE_SENSOR
bool on_sensor(sensor::Sensor *sensor) override;
bool on_sensor(sensor::Sensor *obj) override;
#endif
#ifdef USE_SWITCH
bool on_switch(switch_::Switch *a_switch) override;
bool on_switch(switch_::Switch *obj) override;
#endif
#ifdef USE_BUTTON
bool on_button(button::Button *button) override;
bool on_button(button::Button *obj) override;
#endif
#ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
bool on_text_sensor(text_sensor::TextSensor *obj) override;
#endif
#ifdef USE_CLIMATE
bool on_climate(climate::Climate *climate) override;
bool on_climate(climate::Climate *obj) override;
#endif
#ifdef USE_NUMBER
bool on_number(number::Number *number) override;
bool on_number(number::Number *obj) override;
#endif
#ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *date) override;
bool on_date(datetime::DateEntity *obj) override;
#endif
#ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *time) override;
bool on_time(datetime::TimeEntity *obj) override;
#endif
#ifdef USE_DATETIME_DATETIME
bool on_datetime(datetime::DateTimeEntity *datetime) override;
bool on_datetime(datetime::DateTimeEntity *obj) override;
#endif
#ifdef USE_TEXT
bool on_text(text::Text *text) override;
bool on_text(text::Text *obj) override;
#endif
#ifdef USE_SELECT
bool on_select(select::Select *select) override;
bool on_select(select::Select *obj) override;
#endif
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
bool on_lock(lock::Lock *obj) override;
#endif
#ifdef USE_VALVE
bool on_valve(valve::Valve *valve) override;
bool on_valve(valve::Valve *obj) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *obj) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override;
bool on_event(event::Event *obj) override;
#endif
#ifdef USE_UPDATE
bool on_update(update::UpdateEntity *update) override;
bool on_update(update::UpdateEntity *obj) override;
#endif
bool completed() { return this->state_ == IteratorState::NONE; }
protected:
WebServer *web_server_;
const WebServer *web_server_;
#ifdef USE_ARDUINO
DeferredUpdateEventSource *events_;
#endif
#ifdef USE_ESP_IDF
esphome::web_server_idf::AsyncEventSource *events_;
#endif
};
} // namespace web_server

View File

@ -72,8 +72,146 @@ UrlMatch match_url(const std::string &url, bool only_domain = false) {
return match;
}
WebServer::WebServer(web_server_base::WebServerBase *base)
: base_(base), entities_iterator_(ListEntitiesIterator(this)) {
#ifdef USE_ARDUINO
// helper for allowing only unique entries in the queue
void DeferredUpdateEventSource::deq_push_back_with_dedup_(void *source, message_generator_t *message_generator) {
DeferredEvent item(source, message_generator);
auto iter = std::find_if(this->deferred_queue_.begin(), this->deferred_queue_.end(),
[&item](const DeferredEvent &test) -> bool { return test == item; });
if (iter != this->deferred_queue_.end()) {
(*iter) = item;
} else {
this->deferred_queue_.push_back(item);
}
}
void DeferredUpdateEventSource::process_deferred_queue_() {
while (!deferred_queue_.empty()) {
DeferredEvent &de = deferred_queue_.front();
std::string message = de.message_generator_(web_server_, de.source_);
if (this->try_send(message.c_str(), "state")) {
// O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
deferred_queue_.erase(deferred_queue_.begin());
} else {
break;
}
}
}
void DeferredUpdateEventSource::loop() {
process_deferred_queue_();
if (!this->entities_iterator_.completed())
this->entities_iterator_.advance();
}
void DeferredUpdateEventSource::deferrable_send_state(void *source, const char *event_type,
message_generator_t *message_generator) {
// allow all json "details_all" to go through before publishing bare state events, this avoids unnamed entries showing
// up in the web GUI and reduces event load during initial connect
if (!entities_iterator_.completed() && 0 != strcmp(event_type, "state_detail_all"))
return;
if (source == nullptr)
return;
if (event_type == nullptr)
return;
if (message_generator == nullptr)
return;
if (0 != strcmp(event_type, "state_detail_all") && 0 != strcmp(event_type, "state")) {
ESP_LOGE(TAG, "Can't defer non-state event");
}
if (!deferred_queue_.empty())
process_deferred_queue_();
if (!deferred_queue_.empty()) {
// deferred queue still not empty which means downstream event queue full, no point trying to send first
deq_push_back_with_dedup_(source, message_generator);
} else {
std::string message = message_generator(web_server_, source);
if (!this->try_send(message.c_str(), "state")) {
deq_push_back_with_dedup_(source, message_generator);
}
}
}
// used for logs plus the initial ping/config
void DeferredUpdateEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id,
uint32_t reconnect) {
this->send(message, event, id, reconnect);
}
void DeferredUpdateEventSourceList::loop() {
for (DeferredUpdateEventSource *dues : *this) {
dues->loop();
}
}
void DeferredUpdateEventSourceList::deferrable_send_state(void *source, const char *event_type,
message_generator_t *message_generator) {
for (DeferredUpdateEventSource *dues : *this) {
dues->deferrable_send_state(source, event_type, message_generator);
}
}
void DeferredUpdateEventSourceList::try_send_nodefer(const char *message, const char *event, uint32_t id,
uint32_t reconnect) {
for (DeferredUpdateEventSource *dues : *this) {
dues->try_send_nodefer(message, event, id, reconnect);
}
}
void DeferredUpdateEventSourceList::add_new_client(WebServer *ws, AsyncWebServerRequest *request) {
DeferredUpdateEventSource *es = new DeferredUpdateEventSource(ws, "/events");
this->push_back(es);
es->onConnect([this, ws, es](AsyncEventSourceClient *client) {
ws->defer([this, ws, es]() { this->on_client_connect_(ws, es); });
});
es->onDisconnect([this, ws](AsyncEventSource *source, AsyncEventSourceClient *client) {
ws->defer([this, source]() { this->on_client_disconnect_((DeferredUpdateEventSource *) source); });
});
es->handleRequest(request);
}
void DeferredUpdateEventSourceList::on_client_connect_(WebServer *ws, DeferredUpdateEventSource *source) {
// Configure reconnect timeout and send config
// this should always go through since the AsyncEventSourceClient event queue is empty on connect
std::string message = ws->get_config_json();
source->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
for (auto &group : ws->sorting_groups_) {
message = json::build_json([group](JsonObject root) {
root["name"] = group.second.name;
root["sorting_weight"] = group.second.weight;
});
// up to 31 groups should be able to be queued initially without defer
source->try_send_nodefer(message.c_str(), "sorting_group");
}
source->entities_iterator_.begin(ws->include_internal_);
// just dump them all up-front and take advantage of the deferred queue
// on second thought that takes too long, but leaving the commented code here for debug purposes
// while(!source->entities_iterator_.completed()) {
// source->entities_iterator_.advance();
//}
}
void DeferredUpdateEventSourceList::on_client_disconnect_(DeferredUpdateEventSource *source) {
// This method was called via WebServer->defer() and is no longer executing in the
// context of the network callback. The object is now dead and can be safely deleted.
this->remove(source);
delete source; // NOLINT
}
#endif
WebServer::WebServer(web_server_base::WebServerBase *base) : base_(base) {
#ifdef USE_ESP32
to_schedule_lock_ = xSemaphoreCreateMutex();
#endif
@ -101,34 +239,27 @@ void WebServer::setup() {
this->setup_controller(this->include_internal_);
this->base_->init();
this->events_.onConnect([this](AsyncEventSourceClient *client) {
// Configure reconnect timeout and send config
client->send(this->get_config_json().c_str(), "ping", millis(), 30000);
for (auto &group : this->sorting_groups_) {
client->send(json::build_json([group](JsonObject root) {
root["name"] = group.second.name;
root["sorting_weight"] = group.second.weight;
}).c_str(),
"sorting_group");
}
this->entities_iterator_.begin(this->include_internal_);
});
#ifdef USE_LOGGER
if (logger::global_logger != nullptr && this->expose_log_) {
logger::global_logger->add_on_log_callback(
[this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); });
// logs are not deferred, the memory overhead would be too large
[this](int level, const char *tag, const char *message) {
this->events_.try_send_nodefer(message, "log", millis());
});
}
#endif
#ifdef USE_ESP_IDF
this->base_->add_handler(&this->events_);
#endif
this->base_->add_handler(this);
if (this->allow_ota_)
this->base_->add_ota_handler();
this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); });
// doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly
// getting a lot of events
this->set_interval(10000, [this]() { this->events_.try_send_nodefer("", "ping", millis(), 30000); });
}
void WebServer::loop() {
#ifdef USE_ESP32
@ -147,7 +278,8 @@ void WebServer::loop() {
}
}
#endif
this->entities_iterator_.advance();
this->events_.loop();
}
void WebServer::dump_config() {
ESP_LOGCONFIG(TAG, "Web Server:");
@ -219,9 +351,9 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
#ifdef USE_SENSOR
void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", sensor_state_json_generator);
}
void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (sensor::Sensor *obj : App.get_sensors()) {
@ -240,6 +372,12 @@ void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::sensor_state_json_generator(WebServer *web_server, void *source) {
return web_server->sensor_json((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_STATE);
}
std::string WebServer::sensor_all_json_generator(WebServer *web_server, void *source) {
return web_server->sensor_json((sensor::Sensor *) (source), ((sensor::Sensor *) (source))->state, DETAIL_ALL);
}
std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
std::string state;
@ -267,9 +405,9 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
#ifdef USE_TEXT_SENSOR
void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", text_sensor_state_json_generator);
}
void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (text_sensor::TextSensor *obj : App.get_text_sensors()) {
@ -288,6 +426,14 @@ void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const
}
request->send(404);
}
std::string WebServer::text_sensor_state_json_generator(WebServer *web_server, void *source) {
return web_server->text_sensor_json((text_sensor::TextSensor *) (source),
((text_sensor::TextSensor *) (source))->state, DETAIL_STATE);
}
std::string WebServer::text_sensor_all_json_generator(WebServer *web_server, void *source) {
return web_server->text_sensor_json((text_sensor::TextSensor *) (source),
((text_sensor::TextSensor *) (source))->state, DETAIL_ALL);
}
std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value,
JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
@ -306,9 +452,9 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std:
#ifdef USE_SWITCH
void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", switch_state_json_generator);
}
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (switch_::Switch *obj : App.get_switches()) {
@ -339,6 +485,12 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::switch_state_json_generator(WebServer *web_server, void *source) {
return web_server->switch_json((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_STATE);
}
std::string WebServer::switch_all_json_generator(WebServer *web_server, void *source) {
return web_server->switch_json((switch_::Switch *) (source), ((switch_::Switch *) (source))->state, DETAIL_ALL);
}
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
@ -379,6 +531,12 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::button_state_json_generator(WebServer *web_server, void *source) {
return web_server->button_json((button::Button *) (source), DETAIL_STATE);
}
std::string WebServer::button_all_json_generator(WebServer *web_server, void *source) {
return web_server->button_json((button::Button *) (source), DETAIL_ALL);
}
std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "button-" + obj->get_object_id(), start_config);
@ -396,9 +554,9 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config)
#ifdef USE_BINARY_SENSOR
void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", binary_sensor_state_json_generator);
}
void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) {
@ -417,6 +575,14 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con
}
request->send(404);
}
std::string WebServer::binary_sensor_state_json_generator(WebServer *web_server, void *source) {
return web_server->binary_sensor_json((binary_sensor::BinarySensor *) (source),
((binary_sensor::BinarySensor *) (source))->state, DETAIL_STATE);
}
std::string WebServer::binary_sensor_all_json_generator(WebServer *web_server, void *source) {
return web_server->binary_sensor_json((binary_sensor::BinarySensor *) (source),
((binary_sensor::BinarySensor *) (source))->state, DETAIL_ALL);
}
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value,
@ -435,9 +601,9 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool
#ifdef USE_FAN
void WebServer::on_fan_update(fan::Fan *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", fan_state_json_generator);
}
void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (fan::Fan *obj : App.get_fans()) {
@ -494,6 +660,12 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
}
request->send(404);
}
std::string WebServer::fan_state_json_generator(WebServer *web_server, void *source) {
return web_server->fan_json((fan::Fan *) (source), DETAIL_STATE);
}
std::string WebServer::fan_all_json_generator(WebServer *web_server, void *source) {
return web_server->fan_json((fan::Fan *) (source), DETAIL_ALL);
}
std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state,
@ -519,9 +691,9 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
#ifdef USE_LIGHT
void WebServer::on_light_update(light::LightState *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", light_state_json_generator);
}
void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (light::LightState *obj : App.get_lights()) {
@ -613,6 +785,12 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
}
request->send(404);
}
std::string WebServer::light_state_json_generator(WebServer *web_server, void *source) {
return web_server->light_json((light::LightState *) (source), DETAIL_STATE);
}
std::string WebServer::light_all_json_generator(WebServer *web_server, void *source) {
return web_server->light_json((light::LightState *) (source), DETAIL_ALL);
}
std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "light-" + obj->get_object_id(), start_config);
@ -638,9 +816,9 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
#ifdef USE_COVER
void WebServer::on_cover_update(cover::Cover *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", cover_state_json_generator);
}
void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (cover::Cover *obj : App.get_covers()) {
@ -698,6 +876,12 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
}
request->send(404);
}
std::string WebServer::cover_state_json_generator(WebServer *web_server, void *source) {
return web_server->cover_json((cover::Cover *) (source), DETAIL_STATE);
}
std::string WebServer::cover_all_json_generator(WebServer *web_server, void *source) {
return web_server->cover_json((cover::Cover *) (source), DETAIL_STATE);
}
std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
@ -722,9 +906,9 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
#ifdef USE_NUMBER
void WebServer::on_number_update(number::Number *obj, float state) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", number_state_json_generator);
}
void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_numbers()) {
@ -760,6 +944,12 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
request->send(404);
}
std::string WebServer::number_state_json_generator(WebServer *web_server, void *source) {
return web_server->number_json((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_STATE);
}
std::string WebServer::number_all_json_generator(WebServer *web_server, void *source) {
return web_server->number_json((number::Number *) (source), ((number::Number *) (source))->state, DETAIL_ALL);
}
std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_id(root, obj, "number-" + obj->get_object_id(), start_config);
@ -796,9 +986,9 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
#ifdef USE_DATETIME_DATE
void WebServer::on_date_update(datetime::DateEntity *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->date_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", date_state_json_generator);
}
void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_dates()) {
@ -827,7 +1017,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
}
if (request->hasParam("value")) {
std::string value = request->getParam("value")->value().c_str();
std::string value = request->getParam("value")->value().c_str(); // NOLINT
call.set_date(value);
}
@ -838,6 +1028,12 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
request->send(404);
}
std::string WebServer::date_state_json_generator(WebServer *web_server, void *source) {
return web_server->date_json((datetime::DateEntity *) (source), DETAIL_STATE);
}
std::string WebServer::date_all_json_generator(WebServer *web_server, void *source) {
return web_server->date_json((datetime::DateEntity *) (source), DETAIL_ALL);
}
std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "date-" + obj->get_object_id(), start_config);
@ -858,9 +1054,9 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con
#ifdef USE_DATETIME_TIME
void WebServer::on_time_update(datetime::TimeEntity *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->time_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", time_state_json_generator);
}
void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_times()) {
@ -889,7 +1085,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat
}
if (request->hasParam("value")) {
std::string value = request->getParam("value")->value().c_str();
std::string value = request->getParam("value")->value().c_str(); // NOLINT
call.set_time(value);
}
@ -899,6 +1095,12 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat
}
request->send(404);
}
std::string WebServer::time_state_json_generator(WebServer *web_server, void *source) {
return web_server->time_json((datetime::TimeEntity *) (source), DETAIL_STATE);
}
std::string WebServer::time_all_json_generator(WebServer *web_server, void *source) {
return web_server->time_json((datetime::TimeEntity *) (source), DETAIL_ALL);
}
std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "time-" + obj->get_object_id(), start_config);
@ -919,9 +1121,9 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con
#ifdef USE_DATETIME_DATETIME
void WebServer::on_datetime_update(datetime::DateTimeEntity *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->datetime_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", datetime_state_json_generator);
}
void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_datetimes()) {
@ -950,7 +1152,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur
}
if (request->hasParam("value")) {
std::string value = request->getParam("value")->value().c_str();
std::string value = request->getParam("value")->value().c_str(); // NOLINT
call.set_datetime(value);
}
@ -960,6 +1162,12 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur
}
request->send(404);
}
std::string WebServer::datetime_state_json_generator(WebServer *web_server, void *source) {
return web_server->datetime_json((datetime::DateTimeEntity *) (source), DETAIL_STATE);
}
std::string WebServer::datetime_all_json_generator(WebServer *web_server, void *source) {
return web_server->datetime_json((datetime::DateTimeEntity *) (source), DETAIL_ALL);
}
std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config);
@ -981,9 +1189,9 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s
#ifdef USE_TEXT
void WebServer::on_text_update(text::Text *obj, const std::string &state) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->text_json(obj, state, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", text_state_json_generator);
}
void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_texts()) {
@ -1008,7 +1216,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat
auto call = obj->make_call();
if (request->hasParam("value")) {
String value = request->getParam("value")->value();
call.set_value(value.c_str());
call.set_value(value.c_str()); // NOLINT
}
this->defer([call]() mutable { call.perform(); });
@ -1018,6 +1226,12 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat
request->send(404);
}
std::string WebServer::text_state_json_generator(WebServer *web_server, void *source) {
return web_server->text_json((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_STATE);
}
std::string WebServer::text_all_json_generator(WebServer *web_server, void *source) {
return web_server->text_json((text::Text *) (source), ((text::Text *) (source))->state, DETAIL_ALL);
}
std::string WebServer::text_json(text::Text *obj, const std::string &value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_id(root, obj, "text-" + obj->get_object_id(), start_config);
@ -1045,9 +1259,9 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
#ifdef USE_SELECT
void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", select_state_json_generator);
}
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_selects()) {
@ -1074,7 +1288,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
if (request->hasParam("option")) {
auto option = request->getParam("option")->value();
call.set_option(option.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations)
call.set_option(option.c_str()); // NOLINT
}
this->schedule_([call]() mutable { call.perform(); });
@ -1083,6 +1297,12 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::select_state_json_generator(WebServer *web_server, void *source) {
return web_server->select_json((select::Select *) (source), ((select::Select *) (source))->state, DETAIL_STATE);
}
std::string WebServer::select_all_json_generator(WebServer *web_server, void *source) {
return web_server->select_json((select::Select *) (source), ((select::Select *) (source))->state, DETAIL_ALL);
}
std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) {
return json::build_json([this, obj, value, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config);
@ -1107,9 +1327,9 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
#ifdef USE_CLIMATE
void WebServer::on_climate_update(climate::Climate *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", climate_state_json_generator);
}
void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_climates()) {
@ -1126,6 +1346,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
request->send(200, "application/json", data.c_str());
return;
}
if (match.method != "set") {
request->send(404);
return;
@ -1135,17 +1356,17 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
if (request->hasParam("mode")) {
auto mode = request->getParam("mode")->value();
call.set_mode(mode.c_str());
call.set_mode(mode.c_str()); // NOLINT
}
if (request->hasParam("fan_mode")) {
auto mode = request->getParam("fan_mode")->value();
call.set_fan_mode(mode.c_str());
call.set_fan_mode(mode.c_str()); // NOLINT
}
if (request->hasParam("swing_mode")) {
auto mode = request->getParam("swing_mode")->value();
call.set_swing_mode(mode.c_str());
call.set_swing_mode(mode.c_str()); // NOLINT
}
if (request->hasParam("target_temperature_high")) {
@ -1172,6 +1393,12 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
}
request->send(404);
}
std::string WebServer::climate_state_json_generator(WebServer *web_server, void *source) {
return web_server->climate_json((climate::Climate *) (source), DETAIL_STATE);
}
std::string WebServer::climate_all_json_generator(WebServer *web_server, void *source) {
return web_server->climate_json((climate::Climate *) (source), DETAIL_ALL);
}
std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
@ -1268,9 +1495,9 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
#ifdef USE_LOCK
void WebServer::on_lock_update(lock::Lock *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", lock_state_json_generator);
}
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (lock::Lock *obj : App.get_locks()) {
@ -1301,6 +1528,12 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
}
request->send(404);
}
std::string WebServer::lock_state_json_generator(WebServer *web_server, void *source) {
return web_server->lock_json((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_STATE);
}
std::string WebServer::lock_all_json_generator(WebServer *web_server, void *source) {
return web_server->lock_json((lock::Lock *) (source), ((lock::Lock *) (source))->state, DETAIL_ALL);
}
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
return json::build_json([this, 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,
@ -1319,9 +1552,9 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet
#ifdef USE_VALVE
void WebServer::on_valve_update(valve::Valve *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->valve_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", valve_state_json_generator);
}
void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (valve::Valve *obj : App.get_valves()) {
@ -1372,6 +1605,12 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
}
request->send(404);
}
std::string WebServer::valve_state_json_generator(WebServer *web_server, void *source) {
return web_server->valve_json((valve::Valve *) (source), DETAIL_STATE);
}
std::string WebServer::valve_all_json_generator(WebServer *web_server, void *source) {
return web_server->valve_json((valve::Valve *) (source), DETAIL_ALL);
}
std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
@ -1394,9 +1633,9 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
#ifdef USE_ALARM_CONTROL_PANEL
void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", alarm_control_panel_state_json_generator);
}
void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (alarm_control_panel::AlarmControlPanel *obj : App.get_alarm_control_panels()) {
@ -1416,7 +1655,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
auto call = obj->make_call();
if (request->hasParam("code")) {
call.set_code(request->getParam("code")->value().c_str());
call.set_code(request->getParam("code")->value().c_str()); // NOLINT
}
if (match.method == "disarm") {
@ -1440,6 +1679,16 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
}
request->send(404);
}
std::string WebServer::alarm_control_panel_state_json_generator(WebServer *web_server, void *source) {
return web_server->alarm_control_panel_json((alarm_control_panel::AlarmControlPanel *) (source),
((alarm_control_panel::AlarmControlPanel *) (source))->get_state(),
DETAIL_STATE);
}
std::string WebServer::alarm_control_panel_all_json_generator(WebServer *web_server, void *source) {
return web_server->alarm_control_panel_json((alarm_control_panel::AlarmControlPanel *) (source),
((alarm_control_panel::AlarmControlPanel *) (source))->get_state(),
DETAIL_ALL);
}
std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
alarm_control_panel::AlarmControlPanelState value,
JsonDetail start_config) {
@ -1461,8 +1710,9 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro
#ifdef USE_EVENT
void WebServer::on_event(event::Event *obj, const std::string &event_type) {
this->events_.send(this->event_json(obj, event_type, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", event_state_json_generator);
}
void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (event::Event *obj : App.get_events()) {
if (obj->get_object_id() != match.id)
@ -1481,6 +1731,14 @@ void WebServer::handle_event_request(AsyncWebServerRequest *request, const UrlMa
}
request->send(404);
}
std::string WebServer::event_state_json_generator(WebServer *web_server, void *source) {
return web_server->event_json((event::Event *) (source), *(((event::Event *) (source))->last_event_type),
DETAIL_STATE);
}
std::string WebServer::event_all_json_generator(WebServer *web_server, void *source) {
return web_server->event_json((event::Event *) (source), *(((event::Event *) (source))->last_event_type), DETAIL_ALL);
}
std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) {
return json::build_json([this, obj, event_type, start_config](JsonObject root) {
set_json_id(root, obj, "event-" + obj->get_object_id(), start_config);
@ -1506,9 +1764,9 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty
#ifdef USE_UPDATE
void WebServer::on_update(update::UpdateEntity *obj) {
if (this->events_.count() == 0)
if (this->events_.empty())
return;
this->events_.send(this->update_json(obj, DETAIL_STATE).c_str(), "state");
this->events_.deferrable_send_state(obj, "state", update_state_json_generator);
}
void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (update::UpdateEntity *obj : App.get_updates()) {
@ -1537,6 +1795,12 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM
}
request->send(404);
}
std::string WebServer::update_state_json_generator(WebServer *web_server, void *source) {
return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE);
}
std::string WebServer::update_all_json_generator(WebServer *web_server, void *source) {
return web_server->update_json((update::UpdateEntity *) (source), DETAIL_STATE);
}
std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {
set_json_id(root, obj, "update-" + obj->get_object_id(), start_config);
@ -1575,6 +1839,12 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
if (request->url() == "/")
return true;
#ifdef USE_ARDUINO
if (request->url() == "/events") {
return true;
}
#endif
#ifdef USE_WEBSERVER_CSS_INCLUDE
if (request->url() == "/0.css")
return true;
@ -1597,7 +1867,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
}
#endif
UrlMatch match = match_url(request->url().c_str(), true);
UrlMatch match = match_url(request->url().c_str(), true); // NOLINT
if (!match.valid)
return false;
#ifdef USE_SENSOR
@ -1708,6 +1978,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
return;
}
#ifdef USE_ARDUINO
if (request->url() == "/events") {
this->events_.add_new_client(this, request);
return;
}
#endif
#ifdef USE_WEBSERVER_CSS_INCLUDE
if (request->url() == "/0.css") {
this->handle_css_request(request);
@ -1729,7 +2006,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
}
#endif
UrlMatch match = match_url(request->url().c_str());
UrlMatch match = match_url(request->url().c_str()); // NOLINT
#ifdef USE_SENSOR
if (match.domain == "sensor") {
this->handle_sensor_request(request, match);

View File

@ -8,7 +8,11 @@
#include "esphome/core/controller.h"
#include "esphome/core/entity_base.h"
#include <functional>
#include <list>
#include <map>
#include <string>
#include <utility>
#include <vector>
#ifdef USE_ESP32
#include <freertos/FreeRTOS.h>
@ -54,6 +58,85 @@ struct SortingGroup {
enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
/*
In order to defer updates in arduino mode, we need to create one AsyncEventSource per incoming request to /events.
This is because only minimal changes were made to the ESPAsyncWebServer lib_dep, it was undesirable to put deferred
update logic into that library. We need one deferred queue per connection so instead of one AsyncEventSource with
multiple clients, we have multiple event sources with one client each. This is slightly awkward which is why it's
implemented in a more straightforward way for ESP-IDF. Arudino platform will eventually go away and this workaround
can be forgotten.
*/
#ifdef USE_ARDUINO
using message_generator_t = std::string(WebServer *, void *);
class DeferredUpdateEventSourceList;
class DeferredUpdateEventSource : public AsyncEventSource {
friend class DeferredUpdateEventSourceList;
/*
This class holds a pointer to the source component that wants to publish a state event, and a pointer to a function
that will lazily generate that event. The two pointers allow dedup in the deferred queue if multiple publishes for
the same component are backed up, and take up only 8 bytes of memory. The entry in the deferred queue (a
std::vector) is the DeferredEvent instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per
entry (and no heap fragmentation). Even 100 backed up events (you'd have to have at least 100 sensors publishing
because of dedup) would take up only 0.8 kB.
*/
struct DeferredEvent {
friend class DeferredUpdateEventSource;
protected:
void *source_;
message_generator_t *message_generator_;
public:
DeferredEvent(void *source, message_generator_t *message_generator)
: source_(source), message_generator_(message_generator) {}
bool operator==(const DeferredEvent &test) const {
return (source_ == test.source_ && message_generator_ == test.message_generator_);
}
} __attribute__((packed));
protected:
// surface a couple methods from the base class
using AsyncEventSource::handleRequest;
using AsyncEventSource::try_send;
ListEntitiesIterator entities_iterator_;
// vector is used very specifically for its zero memory overhead even though items are popped from the front (memory
// footprint is more important than speed here)
std::vector<DeferredEvent> deferred_queue_;
WebServer *web_server_;
// helper for allowing only unique entries in the queue
void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator);
void process_deferred_queue_();
public:
DeferredUpdateEventSource(WebServer *ws, const String &url)
: AsyncEventSource(url), entities_iterator_(ListEntitiesIterator(ws, this)), web_server_(ws) {}
void loop();
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
void try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
};
class DeferredUpdateEventSourceList : public std::list<DeferredUpdateEventSource *> {
protected:
void on_client_connect_(WebServer *ws, DeferredUpdateEventSource *source);
void on_client_disconnect_(DeferredUpdateEventSource *source);
public:
void loop();
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
void try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
void add_new_client(WebServer *ws, AsyncWebServerRequest *request);
};
#endif
/** This class allows users to create a web server with their ESP nodes.
*
* Behind the scenes it's using AsyncWebServer to set up the server. It exposes 3 things:
@ -64,6 +147,10 @@ enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
* can be found under https://esphome.io/web-api/index.html.
*/
class WebServer : public Controller, public Component, public AsyncWebHandler {
#ifdef USE_ARDUINO
friend class DeferredUpdateEventSourceList;
#endif
public:
WebServer(web_server_base::WebServerBase *base);
@ -153,6 +240,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a sensor request under '/sensor/<id>'.
void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string sensor_state_json_generator(WebServer *web_server, void *source);
static std::string sensor_all_json_generator(WebServer *web_server, void *source);
/// Dump the sensor state with its value as a JSON string.
std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config);
#endif
@ -163,6 +252,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a switch request under '/switch/<id>/</turn_on/turn_off/toggle>'.
void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string switch_state_json_generator(WebServer *web_server, void *source);
static std::string switch_all_json_generator(WebServer *web_server, void *source);
/// Dump the switch state with its value as a JSON string.
std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config);
#endif
@ -171,6 +262,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a button request under '/button/<id>/press'.
void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string button_state_json_generator(WebServer *web_server, void *source);
static std::string button_all_json_generator(WebServer *web_server, void *source);
/// Dump the button details with its value as a JSON string.
std::string button_json(button::Button *obj, JsonDetail start_config);
#endif
@ -181,6 +274,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a binary sensor request under '/binary_sensor/<id>'.
void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string binary_sensor_state_json_generator(WebServer *web_server, void *source);
static std::string binary_sensor_all_json_generator(WebServer *web_server, void *source);
/// Dump the binary sensor state with its value as a JSON string.
std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config);
#endif
@ -191,6 +286,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a fan request under '/fan/<id>/</turn_on/turn_off/toggle>'.
void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string fan_state_json_generator(WebServer *web_server, void *source);
static std::string fan_all_json_generator(WebServer *web_server, void *source);
/// Dump the fan state as a JSON string.
std::string fan_json(fan::Fan *obj, JsonDetail start_config);
#endif
@ -201,6 +298,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a light request under '/light/<id>/</turn_on/turn_off/toggle>'.
void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string light_state_json_generator(WebServer *web_server, void *source);
static std::string light_all_json_generator(WebServer *web_server, void *source);
/// Dump the light state as a JSON string.
std::string light_json(light::LightState *obj, JsonDetail start_config);
#endif
@ -211,6 +310,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a text sensor request under '/text_sensor/<id>'.
void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string text_sensor_state_json_generator(WebServer *web_server, void *source);
static std::string text_sensor_all_json_generator(WebServer *web_server, void *source);
/// Dump the text sensor state with its value as a JSON string.
std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config);
#endif
@ -221,6 +322,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a cover request under '/cover/<id>/<open/close/stop/set>'.
void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string cover_state_json_generator(WebServer *web_server, void *source);
static std::string cover_all_json_generator(WebServer *web_server, void *source);
/// Dump the cover state as a JSON string.
std::string cover_json(cover::Cover *obj, JsonDetail start_config);
#endif
@ -230,6 +333,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a number request under '/number/<id>'.
void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string number_state_json_generator(WebServer *web_server, void *source);
static std::string number_all_json_generator(WebServer *web_server, void *source);
/// Dump the number state with its value as a JSON string.
std::string number_json(number::Number *obj, float value, JsonDetail start_config);
#endif
@ -239,6 +344,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a date request under '/date/<id>'.
void handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string date_state_json_generator(WebServer *web_server, void *source);
static std::string date_all_json_generator(WebServer *web_server, void *source);
/// Dump the date state with its value as a JSON string.
std::string date_json(datetime::DateEntity *obj, JsonDetail start_config);
#endif
@ -248,6 +355,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a time request under '/time/<id>'.
void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string time_state_json_generator(WebServer *web_server, void *source);
static std::string time_all_json_generator(WebServer *web_server, void *source);
/// Dump the time state with its value as a JSON string.
std::string time_json(datetime::TimeEntity *obj, JsonDetail start_config);
#endif
@ -257,6 +366,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a datetime request under '/datetime/<id>'.
void handle_datetime_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string datetime_state_json_generator(WebServer *web_server, void *source);
static std::string datetime_all_json_generator(WebServer *web_server, void *source);
/// Dump the datetime state with its value as a JSON string.
std::string datetime_json(datetime::DateTimeEntity *obj, JsonDetail start_config);
#endif
@ -266,6 +377,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a text input request under '/text/<id>'.
void handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string text_state_json_generator(WebServer *web_server, void *source);
static std::string text_all_json_generator(WebServer *web_server, void *source);
/// Dump the text state with its value as a JSON string.
std::string text_json(text::Text *obj, const std::string &value, JsonDetail start_config);
#endif
@ -275,6 +388,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a select request under '/select/<id>'.
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string select_state_json_generator(WebServer *web_server, void *source);
static std::string select_all_json_generator(WebServer *web_server, void *source);
/// Dump the select state with its value as a JSON string.
std::string select_json(select::Select *obj, const std::string &value, JsonDetail start_config);
#endif
@ -284,6 +399,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a climate request under '/climate/<id>'.
void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string climate_state_json_generator(WebServer *web_server, void *source);
static std::string climate_all_json_generator(WebServer *web_server, void *source);
/// Dump the climate details
std::string climate_json(climate::Climate *obj, JsonDetail start_config);
#endif
@ -294,6 +411,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a lock request under '/lock/<id>/</lock/unlock/open>'.
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string lock_state_json_generator(WebServer *web_server, void *source);
static std::string lock_all_json_generator(WebServer *web_server, void *source);
/// Dump the lock state with its value as a JSON string.
std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config);
#endif
@ -304,6 +423,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a valve request under '/valve/<id>/<open/close/stop/set>'.
void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string valve_state_json_generator(WebServer *web_server, void *source);
static std::string valve_all_json_generator(WebServer *web_server, void *source);
/// Dump the valve state as a JSON string.
std::string valve_json(valve::Valve *obj, JsonDetail start_config);
#endif
@ -314,6 +435,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a alarm_control_panel request under '/alarm_control_panel/<id>'.
void handle_alarm_control_panel_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string alarm_control_panel_state_json_generator(WebServer *web_server, void *source);
static std::string alarm_control_panel_all_json_generator(WebServer *web_server, void *source);
/// Dump the alarm_control_panel state with its value as a JSON string.
std::string alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config);
@ -322,6 +445,9 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
#ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override;
static std::string event_state_json_generator(WebServer *web_server, void *source);
static std::string event_all_json_generator(WebServer *web_server, void *source);
/// Handle a event request under '/event<id>'.
void handle_event_request(AsyncWebServerRequest *request, const UrlMatch &match);
@ -335,6 +461,8 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
/// Handle a update request under '/update/<id>'.
void handle_update_request(AsyncWebServerRequest *request, const UrlMatch &match);
static std::string update_state_json_generator(WebServer *web_server, void *source);
static std::string update_all_json_generator(WebServer *web_server, void *source);
/// Dump the update state with its value as a JSON string.
std::string update_json(update::UpdateEntity *obj, JsonDetail start_config);
#endif
@ -349,14 +477,19 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
void add_entity_config(EntityBase *entity, float weight, uint64_t group);
void add_sorting_group(uint64_t group_id, const std::string &group_name, float weight);
protected:
void schedule_(std::function<void()> &&f);
friend ListEntitiesIterator;
web_server_base::WebServerBase *base_;
AsyncEventSource events_{"/events"};
ListEntitiesIterator entities_iterator_;
std::map<EntityBase *, SortingComponents> sorting_entitys_;
std::map<uint64_t, SortingGroup> sorting_groups_;
bool include_internal_{false};
protected:
void schedule_(std::function<void()> &&f);
web_server_base::WebServerBase *base_;
#ifdef USE_ARDUINO
DeferredUpdateEventSourceList events_;
#endif
#ifdef USE_ESP_IDF
AsyncEventSource events_{"/events", this};
#endif
#if USE_WEBSERVER_VERSION == 1
const char *css_url_{nullptr};
@ -368,7 +501,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
#ifdef USE_WEBSERVER_JS_INCLUDE
const char *js_include_{nullptr};
#endif
bool include_internal_{false};
bool allow_ota_{true};
bool expose_log_{true};
#ifdef USE_ESP32

View File

@ -37,4 +37,4 @@ async def to_code(config):
cg.add_library("FS", None)
cg.add_library("Update", None)
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2")
cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.3.0")

View File

@ -8,6 +8,8 @@ CONFIG_SCHEMA = cv.All(
cv.only_with_esp_idf,
)
AUTO_LOAD = ["web_server"]
async def to_code(config):
# Increase the maximum supported size of headers section in HTTP request packet to be processed by the server

View File

@ -8,6 +8,10 @@
#include "esp_tls_crypto.h"
#include "utils.h"
#include "esphome/components/web_server/web_server.h"
#include "esphome/components/web_server/list_entities.h"
#include "web_server_idf.h"
namespace esphome {
@ -276,21 +280,37 @@ AsyncEventSource::~AsyncEventSource() {
}
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
auto *rsp = new AsyncEventSourceResponse(request, this); // NOLINT(cppcoreguidelines-owning-memory)
auto *rsp = // NOLINT(cppcoreguidelines-owning-memory)
new AsyncEventSourceResponse(request, this, this->web_server_);
if (this->on_connect_) {
this->on_connect_(rsp);
}
this->sessions_.insert(rsp);
}
void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
void AsyncEventSource::loop() {
for (auto *ses : this->sessions_) {
ses->send(message, event, id, reconnect);
ses->loop();
}
}
AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *request, AsyncEventSource *server)
: server_(server) {
void AsyncEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
for (auto *ses : this->sessions_) {
ses->try_send_nodefer(message, event, id, reconnect);
}
}
void AsyncEventSource::deferrable_send_state(void *source, const char *event_type,
message_generator_t *message_generator) {
for (auto *ses : this->sessions_) {
ses->deferrable_send_state(source, event_type, message_generator);
}
}
AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *request,
esphome::web_server_idf::AsyncEventSource *server,
esphome::web_server::WebServer *ws)
: server_(server), web_server_(ws), entities_iterator_(new esphome::web_server::ListEntitiesIterator(ws, server)) {
httpd_req_t *req = *request;
httpd_resp_set_status(req, HTTPD_200);
@ -309,6 +329,30 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *
this->hd_ = req->handle;
this->fd_ = httpd_req_to_sockfd(req);
// Configure reconnect timeout and send config
// this should always go through since the tcp send buffer is empty on connect
std::string message = ws->get_config_json();
this->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
for (auto &group : ws->sorting_groups_) {
message = json::build_json([group](JsonObject root) {
root["name"] = group.second.name;
root["sorting_weight"] = group.second.weight;
});
// a (very) large number of these should be able to be queued initially without defer
// since the only thing in the send buffer at this point is the initial ping/config
this->try_send_nodefer(message.c_str(), "sorting_group");
}
this->entities_iterator_->begin(ws->include_internal_);
// just dump them all up-front and take advantage of the deferred queue
// on second thought that takes too long, but leaving the commented code here for debug purposes
// while(!this->entities_iterator_->completed()) {
// this->entities_iterator_->advance();
//}
}
void AsyncEventSourceResponse::destroy(void *ptr) {
@ -317,52 +361,155 @@ void AsyncEventSourceResponse::destroy(void *ptr) {
delete rsp; // NOLINT(cppcoreguidelines-owning-memory)
}
void AsyncEventSourceResponse::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
if (this->fd_ == 0) {
// helper for allowing only unique entries in the queue
void AsyncEventSourceResponse::deq_push_back_with_dedup_(void *source, message_generator_t *message_generator) {
DeferredEvent item(source, message_generator);
auto iter = std::find_if(this->deferred_queue_.begin(), this->deferred_queue_.end(),
[&item](const DeferredEvent &test) -> bool { return test == item; });
if (iter != this->deferred_queue_.end()) {
(*iter) = item;
} else {
this->deferred_queue_.push_back(item);
}
}
void AsyncEventSourceResponse::process_deferred_queue_() {
while (!deferred_queue_.empty()) {
DeferredEvent &de = deferred_queue_.front();
std::string message = de.message_generator_(web_server_, de.source_);
if (this->try_send_nodefer(message.c_str(), "state")) {
// O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
deferred_queue_.erase(deferred_queue_.begin());
} else {
break;
}
}
}
void AsyncEventSourceResponse::process_buffer_() {
if (event_buffer_.empty()) {
return;
}
if (event_bytes_sent_ == event_buffer_.size()) {
event_buffer_.resize(0);
event_bytes_sent_ = 0;
return;
}
std::string ev;
int bytes_sent = httpd_socket_send(this->hd_, this->fd_, event_buffer_.c_str() + event_bytes_sent_,
event_buffer_.size() - event_bytes_sent_, 0);
if (bytes_sent == HTTPD_SOCK_ERR_TIMEOUT || bytes_sent == HTTPD_SOCK_ERR_FAIL) {
return;
}
event_bytes_sent_ += bytes_sent;
if (event_bytes_sent_ == event_buffer_.size()) {
event_buffer_.resize(0);
event_bytes_sent_ = 0;
}
}
void AsyncEventSourceResponse::loop() {
process_buffer_();
process_deferred_queue_();
if (!this->entities_iterator_->completed())
this->entities_iterator_->advance();
}
bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char *event, uint32_t id,
uint32_t reconnect) {
if (this->fd_ == 0) {
return false;
}
process_buffer_();
if (!event_buffer_.empty()) {
// there is still pending event data to send first
return false;
}
// 8 spaces are standing in for the hexidecimal chunk length to print later
const char chunk_len_header[] = " " CRLF_STR;
const int chunk_len_header_len = sizeof(chunk_len_header) - 1;
event_buffer_.append(chunk_len_header);
if (reconnect) {
ev.append("retry: ", sizeof("retry: ") - 1);
ev.append(to_string(reconnect));
ev.append(CRLF_STR, CRLF_LEN);
event_buffer_.append("retry: ", sizeof("retry: ") - 1);
event_buffer_.append(to_string(reconnect));
event_buffer_.append(CRLF_STR, CRLF_LEN);
}
if (id) {
ev.append("id: ", sizeof("id: ") - 1);
ev.append(to_string(id));
ev.append(CRLF_STR, CRLF_LEN);
event_buffer_.append("id: ", sizeof("id: ") - 1);
event_buffer_.append(to_string(id));
event_buffer_.append(CRLF_STR, CRLF_LEN);
}
if (event && *event) {
ev.append("event: ", sizeof("event: ") - 1);
ev.append(event);
ev.append(CRLF_STR, CRLF_LEN);
event_buffer_.append("event: ", sizeof("event: ") - 1);
event_buffer_.append(event);
event_buffer_.append(CRLF_STR, CRLF_LEN);
}
if (message && *message) {
ev.append("data: ", sizeof("data: ") - 1);
ev.append(message);
ev.append(CRLF_STR, CRLF_LEN);
event_buffer_.append("data: ", sizeof("data: ") - 1);
event_buffer_.append(message);
event_buffer_.append(CRLF_STR, CRLF_LEN);
}
if (ev.empty()) {
if (event_buffer_.empty()) {
return true;
}
event_buffer_.append(CRLF_STR, CRLF_LEN);
event_buffer_.append(CRLF_STR, CRLF_LEN);
// chunk length header itself and the final chunk terminating CRLF are not counted as part of the chunk
int chunk_len = event_buffer_.size() - CRLF_LEN - chunk_len_header_len;
char chunk_len_str[9];
snprintf(chunk_len_str, 9, "%08x", chunk_len);
std::memcpy(&event_buffer_[0], chunk_len_str, 8);
event_bytes_sent_ = 0;
process_buffer_();
return true;
}
void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *event_type,
message_generator_t *message_generator) {
// allow all json "details_all" to go through before publishing bare state events, this avoids unnamed entries showing
// up in the web GUI and reduces event load during initial connect
if (!entities_iterator_->completed() && 0 != strcmp(event_type, "state_detail_all"))
return;
if (source == nullptr)
return;
if (event_type == nullptr)
return;
if (message_generator == nullptr)
return;
if (0 != strcmp(event_type, "state_detail_all") && 0 != strcmp(event_type, "state")) {
ESP_LOGE(TAG, "Can't defer non-state event");
}
ev.append(CRLF_STR, CRLF_LEN);
process_buffer_();
process_deferred_queue_();
// Sending chunked content prelude
auto cs = str_snprintf("%x" CRLF_STR, 4 * sizeof(ev.size()) + CRLF_LEN, ev.size());
httpd_socket_send(this->hd_, this->fd_, cs.c_str(), cs.size(), 0);
// Sendiing content chunk
httpd_socket_send(this->hd_, this->fd_, ev.c_str(), ev.size(), 0);
// Indicate end of chunk
httpd_socket_send(this->hd_, this->fd_, CRLF_STR, CRLF_LEN, 0);
if (!event_buffer_.empty() || !deferred_queue_.empty()) {
// outgoing event buffer or deferred queue still not empty which means downstream tcp send buffer full, no point
// trying to send first
deq_push_back_with_dedup_(source, message_generator);
} else {
std::string message = message_generator(web_server_, source);
if (!this->try_send_nodefer(message.c_str(), "state")) {
deq_push_back_with_dedup_(source, message_generator);
}
}
}
} // namespace web_server_idf

View File

@ -4,12 +4,18 @@
#include <esp_http_server.h>
#include <functional>
#include <list>
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>
namespace esphome {
namespace web_server {
class WebServer;
class ListEntitiesIterator;
}; // namespace web_server
namespace web_server_idf {
#define F(string_literal) (string_literal)
@ -215,19 +221,58 @@ class AsyncWebHandler {
};
class AsyncEventSource;
class AsyncEventSourceResponse;
using message_generator_t = std::string(esphome::web_server::WebServer *, void *);
/*
This class holds a pointer to the source component that wants to publish a state event, and a pointer to a function
that will lazily generate that event. The two pointers allow dedup in the deferred queue if multiple publishes for
the same component are backed up, and take up only 8 bytes of memory. The entry in the deferred queue (a
std::vector) is the DeferredEvent instance itself (not a pointer to one elsewhere in heap) so still only 8 bytes per
entry (and no heap fragmentation). Even 100 backed up events (you'd have to have at least 100 sensors publishing
because of dedup) would take up only 0.8 kB.
*/
struct DeferredEvent {
friend class AsyncEventSourceResponse;
protected:
void *source_;
message_generator_t *message_generator_;
public:
DeferredEvent(void *source, message_generator_t *message_generator)
: source_(source), message_generator_(message_generator) {}
bool operator==(const DeferredEvent &test) const {
return (source_ == test.source_ && message_generator_ == test.message_generator_);
}
} __attribute__((packed));
class AsyncEventSourceResponse {
friend class AsyncEventSource;
public:
void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
bool try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
void loop();
protected:
AsyncEventSourceResponse(const AsyncWebServerRequest *request, AsyncEventSource *server);
AsyncEventSourceResponse(const AsyncWebServerRequest *request, esphome::web_server_idf::AsyncEventSource *server,
esphome::web_server::WebServer *ws);
void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator);
void process_deferred_queue_();
void process_buffer_();
static void destroy(void *p);
AsyncEventSource *server_;
httpd_handle_t hd_{};
int fd_{};
std::vector<DeferredEvent> deferred_queue_;
esphome::web_server::WebServer *web_server_;
std::unique_ptr<esphome::web_server::ListEntitiesIterator> entities_iterator_;
std::string event_buffer_{""};
size_t event_bytes_sent_;
};
using AsyncEventSourceClient = AsyncEventSourceResponse;
@ -237,7 +282,7 @@ class AsyncEventSource : public AsyncWebHandler {
using connect_handler_t = std::function<void(AsyncEventSourceClient *)>;
public:
AsyncEventSource(std::string url) : url_(std::move(url)) {}
AsyncEventSource(std::string url, esphome::web_server::WebServer *ws) : url_(std::move(url)), web_server_(ws) {}
~AsyncEventSource() override;
// NOLINTNEXTLINE(readability-identifier-naming)
@ -249,7 +294,10 @@ class AsyncEventSource : public AsyncWebHandler {
// NOLINTNEXTLINE(readability-identifier-naming)
void onConnect(connect_handler_t cb) { this->on_connect_ = std::move(cb); }
void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
void try_send_nodefer(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator);
void loop();
bool empty() { return this->count() == 0; }
size_t count() const { return this->sessions_.size(); }
@ -257,6 +305,7 @@ class AsyncEventSource : public AsyncWebHandler {
std::string url_;
std::set<AsyncEventSourceResponse *> sessions_;
connect_handler_t on_connect_{};
esphome::web_server::WebServer *web_server_;
};
class DefaultHeaders {

View File

@ -62,7 +62,7 @@ lib_deps =
SPI ; spi (Arduino built-in)
Wire ; i2c (Arduino built-int)
heman/AsyncMqttClient-esphome@1.0.0 ; mqtt
esphome/ESPAsyncWebServer-esphome@3.2.2 ; web_server_base
esphome/ESPAsyncWebServer-esphome@3.3.0 ; web_server_base
fastled/FastLED@3.3.2 ; fastled_base
mikalhart/TinyGPSPlus@1.0.2 ; gps
freekode/TM1651@1.0.1 ; tm1651