PatrixNode/src/patrix/core/Config.h

154 lines
3.2 KiB
C++

#ifndef PATRIX_CONFIG_H
#define PATRIX_CONFIG_H
#include <ArduinoJson.h>
#include <LittleFS.h>
#include <utility>
#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<JsonObject>();
}
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<typename T>
bool has(const char *key) {
return json[key].is<T>();
}
template<typename T>
T get(const char *key, T fallback) {
if (json[key].is<T>()) {
return json[key].as<T>();
}
warn("Config key \"%s\" not found!", key);
return fallback;
}
bool setIfNot(const char *key, const int value) {
auto changed = !json[key].is<int>();
if (!changed) {
changed = json[key].as<int>() != 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<double>();
if (!changed) {
changed = json[key].as<double>() != value;
}
if (changed) {
set(key, value);
info("Changed key \"%s\" to: %d", key, value);
}
return changed;
}
template<typename T>
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<long>(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<JsonObject>()) {
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<JsonObject>();
return true;
}
};
#endif