110 lines
4.3 KiB
C++
110 lines
4.3 KiB
C++
#include "fgc/Config.h"
|
|
#include "fgc/Paths.h"
|
|
|
|
#include <cstdlib>
|
|
#include <stdexcept>
|
|
|
|
extern "C" {
|
|
#include "ini.h"
|
|
}
|
|
|
|
namespace fgc {
|
|
|
|
namespace {
|
|
|
|
std::string get(const std::map<std::string, std::string>& kv, const std::string& key,
|
|
const std::string& fallback = "") {
|
|
auto it = kv.find(key);
|
|
return (it != kv.end() && !it->second.empty()) ? it->second : fallback;
|
|
}
|
|
|
|
int getInt(const std::map<std::string, std::string>& kv, const std::string& key, int fallback) {
|
|
auto it = kv.find(key);
|
|
if (it == kv.end() || it->second.empty()) return fallback;
|
|
try {
|
|
return std::stoi(it->second);
|
|
} catch (const std::exception&) {
|
|
throw std::runtime_error("Config key '" + key + "' must be an integer, got '" +
|
|
it->second + "'");
|
|
}
|
|
}
|
|
|
|
bool getBool(const std::map<std::string, std::string>& kv, const std::string& key, bool fallback) {
|
|
auto it = kv.find(key);
|
|
if (it == kv.end() || it->second.empty()) return fallback;
|
|
const std::string& v = it->second;
|
|
if (v == "1" || v == "true" || v == "yes" || v == "on") return true;
|
|
if (v == "0" || v == "false" || v == "no" || v == "off") return false;
|
|
throw std::runtime_error("Config key '" + key + "' must be a boolean, got '" + v + "'");
|
|
}
|
|
|
|
std::string envOr(const char* name, const std::string& fallback) {
|
|
const char* v = std::getenv(name);
|
|
return (v && *v) ? std::string(v) : fallback;
|
|
}
|
|
|
|
// inih callback: flatten "Section.name" => value into the map.
|
|
int iniHandler(void* user, const char* section, const char* name, const char* value) {
|
|
auto* kv = static_cast<std::map<std::string, std::string>*>(user);
|
|
(*kv)[std::string(section) + "." + std::string(name)] = value ? value : "";
|
|
return 1;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
double AppConfig::image_rate() const {
|
|
return general.image_interval > 0 ? 1.0 / general.image_interval : 0.0;
|
|
}
|
|
|
|
AppConfig ConfigLoader::fromMap(const std::map<std::string, std::string>& kv) {
|
|
AppConfig cfg;
|
|
|
|
cfg.general.tower_name = get(kv, "General.tower_name", cfg.general.tower_name);
|
|
cfg.general.image_interval = getInt(kv, "General.image_interval", cfg.general.image_interval);
|
|
cfg.general.debug = getBool(kv, "General.debug", cfg.general.debug);
|
|
|
|
cfg.network.broker_ip = get(kv, "Network.zkms_server_ip", cfg.network.broker_ip);
|
|
// Secrets: environment variables win over the file.
|
|
cfg.network.mqtt_user = envOr("FGC_MQTT_USER", get(kv, "Network.mqtt_user"));
|
|
cfg.network.mqtt_pw = envOr("FGC_MQTT_PW", get(kv, "Network.mqtt_pw"));
|
|
|
|
cfg.serial.device = get(kv, "Serial.device", cfg.serial.device);
|
|
cfg.serial.baud = static_cast<unsigned>(getInt(kv, "Serial.baud", cfg.serial.baud));
|
|
|
|
cfg.camera.ids.clear();
|
|
for (const char* key : {"Camera.id_Cam1", "Camera.id_Cam2", "Camera.id_Cam3", "Camera.id_Cam4"}) {
|
|
std::string id = get(kv, key);
|
|
if (!id.empty()) cfg.camera.ids.push_back(id);
|
|
}
|
|
|
|
std::string out = get(kv, "Paths.output_dir");
|
|
cfg.paths.output_dir = out.empty() ? paths::defaultOutputDir() : paths::expandUser(out);
|
|
|
|
cfg.features.enable_mqtt = getBool(kv, "Features.enable_mqtt", cfg.features.enable_mqtt);
|
|
cfg.features.enable_camera = getBool(kv, "Features.enable_camera", cfg.features.enable_camera);
|
|
cfg.features.enable_serial = getBool(kv, "Features.enable_serial", cfg.features.enable_serial);
|
|
cfg.features.mock_camera = getBool(kv, "Features.mock_camera", cfg.features.mock_camera);
|
|
cfg.features.mock_serial = getBool(kv, "Features.mock_serial", cfg.features.mock_serial);
|
|
|
|
// ---- validation ----
|
|
if (cfg.general.image_interval <= 0)
|
|
throw std::runtime_error("General.image_interval must be > 0 (got " +
|
|
std::to_string(cfg.general.image_interval) + ")");
|
|
if (cfg.serial.baud == 0)
|
|
throw std::runtime_error("Serial.baud must be > 0");
|
|
|
|
return cfg;
|
|
}
|
|
|
|
AppConfig ConfigLoader::loadFromFile(const std::string& path) {
|
|
std::map<std::string, std::string> kv;
|
|
int rc = ini_parse(path.c_str(), iniHandler, &kv);
|
|
if (rc < 0) throw std::runtime_error("Cannot read config file: " + path);
|
|
if (rc > 0)
|
|
throw std::runtime_error("Parse error in config file " + path + " at line " +
|
|
std::to_string(rc));
|
|
return fromMap(kv);
|
|
}
|
|
|
|
} // namespace fgc
|