From 81e9cabf653a83cf2a1918e73f39827218b11478 Mon Sep 17 00:00:00 2001 From: Alexander 'Leo' Bergolth Date: Sat, 26 Mar 2022 19:10:09 +0100 Subject: [PATCH] add /states REST API call that dumps the current component state in a json array --- .../components/web_server/list_entities.cpp | 116 ++++++++---------- esphome/components/web_server/list_entities.h | 2 + esphome/components/web_server/states.cpp | 35 ++++++ esphome/components/web_server/states.h | 30 +++++ esphome/components/web_server/web_server.cpp | 24 ++++ esphome/components/web_server/web_server.h | 4 + 6 files changed, 147 insertions(+), 64 deletions(-) create mode 100644 esphome/components/web_server/states.cpp create mode 100644 esphome/components/web_server/states.h diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index a02f84c34b..9f612c1aff 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -13,159 +13,140 @@ ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_( #ifdef USE_BINARY_SENSOR bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send( - this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state"); - return true; + return this->process( + this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL)); } #endif #ifdef USE_COVER bool ListEntitiesIterator::on_cover(cover::Cover *cover) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->cover_json(cover, DETAIL_ALL)); } #endif #ifdef USE_FAN bool ListEntitiesIterator::on_fan(fan::Fan *fan) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->fan_json(fan, DETAIL_ALL)); } #endif #ifdef USE_LIGHT bool ListEntitiesIterator::on_light(light::LightState *light) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->light_json(light, DETAIL_ALL)); } #endif #ifdef USE_SENSOR bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL)); } #endif #ifdef USE_SWITCH bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(), - "state"); - return true; + return this->process(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL)); } #endif #ifdef USE_BUTTON bool ListEntitiesIterator::on_button(button::Button *button) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->button_json(button, DETAIL_ALL)); } #endif #ifdef USE_TEXT_SENSOR bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send( - this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state"); - return true; + return this->process( + this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL)); } #endif #ifdef USE_LOCK bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL)); } #endif #ifdef USE_VALVE bool ListEntitiesIterator::on_valve(valve::Valve *valve) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->valve_json(valve, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->valve_json(valve, DETAIL_ALL)); } #endif #ifdef USE_CLIMATE bool ListEntitiesIterator::on_climate(climate::Climate *climate) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->climate_json(climate, DETAIL_ALL)); } #endif #ifdef USE_NUMBER bool ListEntitiesIterator::on_number(number::Number *number) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->number_json(number, number->state, DETAIL_ALL)); } #endif #ifdef USE_DATETIME_DATE bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->date_json(date, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->date_json(date, DETAIL_ALL)); } #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"); - return true; + if (!this->has_connected_client()) + return true; + return this->process(this->web_server_->time_json(time, DETAIL_ALL)); } #endif #ifdef USE_DATETIME_DATETIME bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->datetime_json(datetime, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->datetime_json(datetime, DETAIL_ALL)); } #endif #ifdef USE_TEXT bool ListEntitiesIterator::on_text(text::Text *text) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->text_json(text, text->state, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->text_json(text, text->state, DETAIL_ALL)); } #endif #ifdef USE_SELECT bool ListEntitiesIterator::on_select(select::Select *select) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->select_json(select, select->state, DETAIL_ALL)); } #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) + if (!this->has_connected_client()) 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"); - return true; + return this->process( + this->web_server_->alarm_control_panel_json(a_alarm_control_panel, a_alarm_control_panel->get_state(), DETAIL_ALL)); } #endif @@ -173,20 +154,27 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont bool ListEntitiesIterator::on_event(event::Event *event) { // 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"); - return true; + if (!this->has_connected_client()) + return true; + return this->process(this->web_server_->event_json(event, null_event_type, DETAIL_ALL)); } #endif #ifdef USE_UPDATE bool ListEntitiesIterator::on_update(update::UpdateEntity *update) { - if (this->web_server_->events_.count() == 0) + if (!this->has_connected_client()) return true; - this->web_server_->events_.send(this->web_server_->update_json(update, DETAIL_ALL).c_str(), "state"); - return true; + return this->process(this->web_server_->update_json(update, DETAIL_ALL)); } #endif +bool ListEntitiesIterator::has_connected_client() { return this->web_server_->events_.count() > 0; } + +bool ListEntitiesIterator::process(std::string s) { + this->web_server_->events_.send(s.c_str(), "state"); + return true; +} + } // namespace web_server } // namespace esphome #endif diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 53e5bc3355..f987609359 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -75,6 +75,8 @@ class ListEntitiesIterator : public ComponentIterator { protected: WebServer *web_server_; + bool has_connected_client(); + virtual bool process(std::string s); }; } // namespace web_server diff --git a/esphome/components/web_server/states.cpp b/esphome/components/web_server/states.cpp new file mode 100644 index 0000000000..8b487c1943 --- /dev/null +++ b/esphome/components/web_server/states.cpp @@ -0,0 +1,35 @@ +#ifdef USE_ARDUINO + +#include "list_entities.h" +#include "esphome/core/application.h" + +#include "web_server.h" + +namespace esphome { +namespace web_server { + +StatesIterator::StatesIterator(WebServer *web_server) : ListEntitiesIterator::ListEntitiesIterator(web_server) {} + +bool StatesIterator::has_connected_client() { return true; } + +bool StatesIterator::process(std::string s) { + this->str_ = s; + return true; +} + +optional StatesIterator::next() { + while (this->state_ != IteratorState::MAX) { + this->str_ = nullopt; + this->advance(); + if(this->str_.has_value()) { + return this->str_; + } + } + this->str_ = nullopt; + return nullopt; +} + +} // namespace web_server +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server/states.h b/esphome/components/web_server/states.h new file mode 100644 index 0000000000..edeeee18a2 --- /dev/null +++ b/esphome/components/web_server/states.h @@ -0,0 +1,30 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "list_entities.h" +#include "esphome/core/component.h" +#include "esphome/core/component_iterator.h" +#include "esphome/core/defines.h" +#include "esphome/components/web_server_base/web_server_base.h" + +namespace esphome { +namespace web_server { + +class WebServer; + +class StatesIterator : public ListEntitiesIterator { + public: + StatesIterator(WebServer *web_server); + optional next(); + + protected: + // WebServer *web_server_; + optional str_; + virtual bool process(std::string s); +}; + +} // namespace web_server +} // namespace esphome + +#endif // USE_ARDUINO diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 0467023039..d3ecd25986 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -199,6 +199,22 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { } #endif +void WebServer::handle_states_request(AsyncWebServerRequest *request) { + AsyncResponseStream *stream = request->beginResponseStream("text/json"); + StatesIterator states_it = StatesIterator(this); + states_it.begin(); + optional s; + stream->print(F("{")); + int i = 0; + while((s = states_it.next()) != nullopt) { + if (i++) + stream->print(F(",")); + stream->print(s->c_str()); + } + stream->print(F("}\n")); + request->send(stream); +} + #define set_json_id(root, obj, sensor, start_config) \ (root)["id"] = sensor; \ if (((start_config) == DETAIL_ALL)) { \ @@ -1553,6 +1569,9 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { if (request->url() == "/") return true; + if (request->url() == "/states") + return true; + #ifdef USE_WEBSERVER_CSS_INCLUDE if (request->url() == "/0.css") return true; @@ -1686,6 +1705,11 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { return; } + if (request->url() == "/states") { + this->handle_states_request(request); + return; + } + #ifdef USE_WEBSERVER_CSS_INCLUDE if (request->url() == "/0.css") { this->handle_css_request(request); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 8edb678169..39fc521484 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -1,6 +1,7 @@ #pragma once #include "list_entities.h" +#include "states.h" #include "esphome/components/web_server_base/web_server_base.h" #ifdef USE_WEBSERVER @@ -148,6 +149,9 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_pna_cors_request(AsyncWebServerRequest *request); #endif + /// Handle a states request under '/states'. + void handle_states_request(AsyncWebServerRequest *request); + #ifdef USE_SENSOR void on_sensor_update(sensor::Sensor *obj, float state) override; /// Handle a sensor request under '/sensor/'.