#ifndef PATRIX_CONFIG_H #define PATRIX_CONFIG_H #include #include #include #include "filesystem.h" #include "log.h" #define CONFIG_WRITE_DELAY_MILLIS (30 * 1000) class Config final { const String path; unsigned long dirty = 0; bool doForceNextHexBuffer = true; public: JsonDocument json; explicit Config(String path) : path(std::move(path)) { // } ~Config() = default; void read() { fsMount(); auto file = LittleFS.open(path, "r"); if (!file) { error("Failed to open file for config: %s", path.c_str()); return; } if (!deserialize(file)) { json.clear(); json = json.to(); } file.close(); } void write() const { fsMount(); auto write = LittleFS.open(path, "w"); if (!write) { error("Failed to open file for config write: %s", path.c_str()); return; } const auto size = measureJson(json); if (serializeJson(json, write) == size) { char buffer[256]; serializeJson(json, buffer, sizeof buffer); info("Config written: %s => %s", path.c_str(), buffer); } else { error("Failed to write config: %s", path.c_str()); } write.close(); } void loop() { if (dirty != 0 && millis() - dirty > CONFIG_WRITE_DELAY_MILLIS) { dirty = 0; write(); } } template bool has(const char *key) { return json[key].is(); } template T get(const char *key, T fallback) { if (json[key].is()) { return json[key].as(); } warn("Config key \"%s\" not found!", key); return fallback; } bool setIfNot(const char *key, const int value) { auto changed = !json[key].is(); if (!changed) { changed = json[key].as() != value; } if (changed) { set(key, value); info("Changed key \"%s\" to: %d", key, value); } return changed; } bool setIfNot(const char *key, const double value) { auto changed = !json[key].is(); if (!changed) { changed = json[key].as() != value; } if (changed) { set(key, value); info("Changed key \"%s\" to: %d", key, value); } return changed; } template void set(const String& key, T value) { json[key] = value; dirty = millis(); if (dirty == 0) { // avoid special value zero (=> not dirty) dirty--; } } unsigned long getAutoWriteInMillis() const { if (dirty == 0) { return 0; } return CONFIG_WRITE_DELAY_MILLIS - (millis() - dirty); } time_t getAutoWriteAtEpoch() const { if (dirty == 0) { return 0; } return time(nullptr) + static_cast(getAutoWriteInMillis() / 1000); } private: bool deserialize(File file) { if (deserializeJson(json, file) != DeserializationError::Ok) { error("Failed to deserialize config: %s", file.path()); return false; } if (!json.is()) { error("Config not a json-object: %s", file.path()); return false; } char buffer[256]; serializeJson(json, buffer, sizeof buffer); info("Config loaded: %s => %s", path.c_str(), buffer); json = json.as(); return true; } }; #endif