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:
parent
c424fea524
commit
5e44a035a3
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user