yaml as a compressed global array

This commit is contained in:
Gábor Poczkodi 2024-11-09 15:55:57 +01:00
parent 68838a72c8
commit 59809788c9
3 changed files with 141 additions and 21 deletions

View file

@ -25,13 +25,68 @@ CONFIG_SCHEMA = cv.Schema(
)
class BitWriter:
def __init__(self, output, next, bits):
self.output = output
self.buff = 0
self.size = 0
self.next = next
self.bits = bits
def put_bits(self, value, bits):
self.buff = (self.buff << bits) | (value & ((1 << bits) - 1))
self.size += bits
while self.size >= 8:
b = (self.buff >> (self.size - 8)) & 255
self.buff &= (1 << (self.size - 8)) - 1
self.output.append(b)
self.size -= 8
def put_code(self, code):
if self.next == (1 << self.bits):
self.bits += 1
self.put_bits(code, self.bits)
def flush(self):
if self.size > 0:
b = (self.buff << (8 - self.size)) & 255
def compress(s):
input = s.encode("utf-8")
output = bytearray()
codes = {}
for i in range(256):
codes[chr(i)] = i
writer = BitWriter(output, len(codes), 8)
prev = None
for c in input:
pc = prev + chr(c) if prev is not None else chr(c)
if pc not in codes:
writer.put_code(codes[prev])
prev = chr(c)
codes[pc] = writer.next
writer.next += 1
else:
prev = pc
writer.put_code(codes[prev])
writer.flush()
return output
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
store_yaml = await cg.register_component(var, config)
cg.add(store_yaml.set_show_in_dump_config(config[CONF_SHOW_IN_DUMP_CONFIG]))
yaml = strip_default_ids(copy.deepcopy(CORE.config))
dump = yaml_util.dump(yaml, show_secrets=config[CONF_SHOW_SECRETS])
cg.add(store_yaml.set_yaml(dump))
data = compress(dump)
size = len(data)
bytes_as_int = ", ".join(str(x) for x in data)
uint8_t = f"const uint8_t ESPHOME_YAML[{size}] PROGMEM = {{{bytes_as_int}}}"
size_t = f"const size_t ESPHOME_YAML_SIZE = {size}"
cg.add_global(cg.RawExpression(uint8_t))
cg.add_global(cg.RawExpression(size_t))
LogAction = store_yaml_ns.class_("LogAction", automation.Action)

View file

@ -1,5 +1,7 @@
#include "store_yaml.h"
#include "esphome/core/log.h"
#include <string>
#include <map>
namespace esphome {
namespace store_yaml {
@ -13,27 +15,90 @@ void StoreYamlComponent::dump_config() {
}
}
const char *StoreYamlComponent::get_yaml() const { return this->yaml_; }
class Decompressor {
std::map<uint16_t, std::string> codes_; // TODO: replace string with char + index to next char
size_t pos_{0};
uint8_t size_{0};
uint32_t buff_{0};
size_t code_index_;
uint8_t code_width_;
public:
Decompressor() {
for (int i = 0; i < 256; i++) {
this->codes_[i] = std::string(1, (char) i);
}
this->code_index_ = this->codes_.size();
this->code_width_ = 9; // log2next + 1
}
uint32_t get_bits(size_t bits) {
if (this->is_eof())
return UINT32_MAX;
while (this->size_ < bits) {
this->buff_ = (this->buff_ << 8) | ESPHOME_YAML[this->pos_++];
this->size_ += 8;
}
uint32_t value = (this->buff_ >> (this->size_ - bits)) & ((1 << bits) - 1);
this->size_ -= bits;
this->buff_ &= (1 << this->size_) - 1;
return value;
}
std::string get_code() {
if (this->code_index_ == ((1 << this->code_width_) - 1)) {
this->code_width_++;
}
uint32_t c = get_bits(this->code_width_);
auto i = this->codes_.find(c);
if (i != this->codes_.end()) {
return i->second;
}
if (c != this->codes_.size()) {
this->pos_ = ESPHOME_YAML_SIZE; // error in input, set eof
}
return std::string();
}
void add_code(const std::string &c) { this->codes_[this->code_index_++] = c; }
bool is_eof() const { return this->pos_ >= ESPHOME_YAML_SIZE; }
};
void StoreYamlComponent::log(bool dump_config) const {
const char *s = this->yaml_;
while (*s) {
const char *e = s;
while (*e && *e++ != '\n')
;
const char *tmp = e;
if (e > s) {
if (e[-1] == '\n')
e--;
std::string row = std::string(s, e - s);
if (dump_config) {
ESP_LOGCONFIG(TAG, "%s", row.c_str());
} else {
ESP_LOGI(TAG, "%s", row.c_str());
Decompressor *dec = new Decompressor();
std::string c = dec->get_code();
std::string prev = c;
std::string yaml = c;
while (!dec->is_eof()) {
std::string c = dec->get_code();
if (c.empty())
c = prev + prev[0];
dec->add_code(prev + c[0]);
prev = c;
yaml += c;
// print line by line because the logger cannot handle too much data
// it also uses less memory
size_t newline = 0;
while (newline != std::string::npos) {
newline = yaml.find('\n');
if (newline != std::string::npos) {
std::string row = yaml.substr(0, newline);
yaml = std::string(yaml.begin() + newline + 1, yaml.end());
if (dump_config) {
ESP_LOGCONFIG(TAG, "%s", row.c_str());
} else {
ESP_LOGI(TAG, "%s", row.c_str());
}
}
}
s = tmp;
}
delete dec;
}
} // namespace store_yaml

View file

@ -2,20 +2,20 @@
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/core/hal.h"
extern const char ESPHOME_YAML[];
extern const uint8_t ESPHOME_YAML[] PROGMEM;
extern const size_t ESPHOME_YAML_SIZE;
namespace esphome {
namespace store_yaml {
class StoreYamlComponent : public Component {
bool show_in_dump_config_{false};
const char *yaml_;
public:
void dump_config() override;
void set_show_in_dump_config(bool show) { this->show_in_dump_config_ = show; }
void set_yaml(const char *yaml) { this->yaml_ = yaml; }
const char *get_yaml() const;
void log(bool dump_config = false) const;
};