diff --git a/esphome/components/store_yaml/__init__.py b/esphome/components/store_yaml/__init__.py index 005885f7df..3f9b5dae53 100644 --- a/esphome/components/store_yaml/__init__.py +++ b/esphome/components/store_yaml/__init__.py @@ -4,12 +4,16 @@ import esphome.config_validation as cv from esphome import automation, yaml_util from esphome.core import CORE from esphome.config import strip_default_ids +from esphome.components import web_server_base +from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID from esphome.const import ( CONF_ID, + CONF_URL, ) CODEOWNERS = ["@gabest11"] +AUTO_LOAD = ["web_server_base"] CONF_SHOW_IN_DUMP_CONFIG = "show_in_dump_config" CONF_SHOW_SECRETS = "show_secrets" @@ -21,6 +25,10 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(StoreYamlComponent), cv.Optional(CONF_SHOW_IN_DUMP_CONFIG, default=False): cv.boolean, cv.Optional(CONF_SHOW_SECRETS, default=False): cv.boolean, + cv.Optional(CONF_URL): cv.string_strict, + cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id( + web_server_base.WebServerBase + ), } ) @@ -88,6 +96,9 @@ async def to_code(config): size_t = f"const size_t ESPHOME_YAML_SIZE = {size}" cg.add_global(cg.RawExpression(uint8_t)) cg.add_global(cg.RawExpression(size_t)) + if CONF_URL in config: + webserver = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) + cg.add(var.set_web_server(webserver, config[CONF_URL])) LogAction = store_yaml_ns.class_("LogAction", automation.Action) diff --git a/esphome/components/store_yaml/decompressor.cpp b/esphome/components/store_yaml/decompressor.cpp index ebe90202bf..d80604aa65 100644 --- a/esphome/components/store_yaml/decompressor.cpp +++ b/esphome/components/store_yaml/decompressor.cpp @@ -19,7 +19,7 @@ void Decompressor::reset() { this->buff_ = 0; this->codes_.clear(); for (uint32_t i = 0; i < 256; i++) { - this->codes_.push_back(Entry{.p = 0, .c = i}); + this->codes_.push_back(Entry{.p = 0, .c = (uint8_t) i}); } this->code_width_ = 9; // log2next + 1 } @@ -88,6 +88,10 @@ std::string Decompressor::get_next() { this->prev_.c = s[0]; this->codes_.push_back(this->prev_); this->prev_.p = code; + if (this->is_eof()) { + // free code table, no longer needed + this->codes_.clear(); + } return s; } diff --git a/esphome/components/store_yaml/decompressor.h b/esphome/components/store_yaml/decompressor.h index 8b6257fb16..f5a17e1cd8 100644 --- a/esphome/components/store_yaml/decompressor.h +++ b/esphome/components/store_yaml/decompressor.h @@ -7,9 +7,11 @@ namespace esphome { namespace store_yaml { struct Entry { - uint32_t p : 24; - uint32_t c : 8; -}; + // uint32_t p : 24; + // uint32_t c : 8; + uint16_t p; // save 1 byte, this reduces the number of code words to 65536, more than enough for configs + uint8_t c; +} __attribute__((packed)); class Decompressor { const uint8_t *data_ptr_; diff --git a/esphome/components/store_yaml/store_yaml.cpp b/esphome/components/store_yaml/store_yaml.cpp index 1d5353a98d..125ba0fde2 100644 --- a/esphome/components/store_yaml/store_yaml.cpp +++ b/esphome/components/store_yaml/store_yaml.cpp @@ -18,6 +18,13 @@ void StoreYamlComponent::dump_config() { } } +void StoreYamlComponent::setup() { + if (this->web_server_ != nullptr) { + this->web_server_->init(); + this->web_server_->add_handler(this); + } +} + void StoreYamlComponent::loop() { if (this->dec_) { std::string row; @@ -34,5 +41,48 @@ void StoreYamlComponent::log() { this->dec_ = make_unique(ESPHOME_YAML, ESPHOME_YAML_SIZE); } +void StoreYamlComponent::set_web_server(web_server_base::WebServerBase *web_server, const std::string &url) { + this->web_server_ = web_server; + this->web_server_url_ = url; +} + +bool StoreYamlComponent::canHandle(AsyncWebServerRequest *request) { + return request->method() == HTTP_GET && request->url() == this->web_server_url_.c_str(); +} + +void StoreYamlComponent::handleRequest(AsyncWebServerRequest *request) { +#ifdef USE_ARDUINO + auto cb = [this](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + uint8_t *ptr = buffer; + // 5KB+ config file with a single character repeating will result in a 100 byte long word, not likely + while (maxLen > 100 && !(this->web_dec_ && this->web_dec_->is_eof())) { + std::string s; + if (!this->web_dec_) { + this->web_dec_ = make_unique(ESPHOME_YAML, ESPHOME_YAML_SIZE); + s = this->web_dec_->get_first(); + } else { + s = this->web_dec_->get_next(); + } + size_t len = std::min(maxLen, s.size()); + memcpy(ptr, s.c_str(), len); + ptr += len; + maxLen -= len; + } + return ptr - buffer; + }; + AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain;charset=UTF-8", cb); +#else + AsyncResponseStream *response = request->beginResponseStream("text/plain;charset=UTF-8"); + auto dec = make_unique(ESPHOME_YAML, ESPHOME_YAML_SIZE); + response->print(dec->get_first().c_str()); + while (!dec->is_eof()) { + response->print(dec->get_next().c_str()); + } + dec = nullptr; +#endif + + request->send(response); +} + } // namespace store_yaml } // namespace esphome diff --git a/esphome/components/store_yaml/store_yaml.h b/esphome/components/store_yaml/store_yaml.h index 3e2d4d0334..b76092ea84 100644 --- a/esphome/components/store_yaml/store_yaml.h +++ b/esphome/components/store_yaml/store_yaml.h @@ -3,6 +3,7 @@ #include "esphome/core/component.h" #include "esphome/core/automation.h" #include "esphome/core/hal.h" +#include "esphome/components/web_server_base/web_server_base.h" #include #include "decompressor.h" @@ -12,15 +13,24 @@ extern const size_t ESPHOME_YAML_SIZE; namespace esphome { namespace store_yaml { -class StoreYamlComponent : public Component { +class StoreYamlComponent : public Component, public AsyncWebHandler { std::unique_ptr dec_; bool show_in_dump_config_{false}; + web_server_base::WebServerBase *web_server_; + std::string web_server_url_; + std::unique_ptr web_dec_; + bool canHandle(AsyncWebServerRequest *request) override; + void handleRequest(AsyncWebServerRequest *request) override; + public: void dump_config() override; + float get_setup_priority() const override { return setup_priority::WIFI - 1.0f; } + void setup() override; void loop() override; void log(); void set_show_in_dump_config(bool show) { this->show_in_dump_config_ = show; } + void set_web_server(web_server_base::WebServerBase *web_server, const std::string &url); }; template class LogAction : public Action, public Parented {