Files
LoraSA/lib/config/config.cpp
2024-12-23 12:17:38 +00:00

394 lines
9.0 KiB
C++

#include "config.h"
void listDir(fs::FS &fs, const char *dirname, uint8_t levels)
{
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root)
{
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory())
{
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file)
{
if (file.isDirectory())
{
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels)
{
listDir(fs, file.path(), levels - 1);
}
}
else
{
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
#define LORA_CONFIG "/lora_config.txt"
Config Config::init()
{
if (SD.cardType() == sdcard_type_t::CARD_NONE)
{
Serial.println("No SD card found, will assume defaults");
return Config();
}
File f = SD.open(LORA_CONFIG, FILE_READ);
if (!f)
{
Serial.println("Listing root directory:");
listDir(SD, "/", 0);
Serial.println("Config file " LORA_CONFIG " not found, will assume defaults");
Config cfg;
if (cfg.create_missing_config)
{
cfg.write_config(LORA_CONFIG);
}
return cfg;
}
Config c = Config();
while (f.available() > 0)
{
String ln = f.readStringUntil('\n');
ParseResult r = parse_config_line(ln);
if (r.error.length() > 0)
{
Serial.printf("%s in '%s', will assume defaults\n", r.error, ln);
return Config();
}
if (r.key.length() == 0)
{
// blank line or comment - skip
continue;
}
// do something with known keys and values
if (r.key.equalsIgnoreCase("print_profile_time"))
{
String v = r.value;
bool p = v.equalsIgnoreCase("true");
if (!p && !v.equalsIgnoreCase("false"))
{
Serial.printf("Expected bool for '%s', found '%s' - ignoring\n",
r.key.c_str(), r.value.c_str());
}
else
{
c.print_profile_time = p;
}
continue;
}
if (r.key.equalsIgnoreCase("log_data_json_interval"))
{
c.log_data_json_interval = r.value.toInt();
continue;
}
if (r.key.equalsIgnoreCase("listen_on_serial0"))
{
c.listen_on_serial0 = r.value;
continue;
}
if (r.key.equalsIgnoreCase("listen_on_serial1"))
{
c.listen_on_serial1 = r.value;
continue;
}
if (r.key.equalsIgnoreCase("listen_on_usb"))
{
c.listen_on_serial0 = r.value;
continue;
}
if (r.key.equalsIgnoreCase("detection_strategy"))
{
c.configureDetectionStrategy(r.value);
continue;
}
Serial.printf("Unknown key '%s' will be ignored\n", r.key);
}
f.close();
return c;
}
String detectionStrategyToStr(Config &c)
{
String res = c.detection_strategy;
if (c.scan_ranges_sz > 0)
{
res += ":";
for (int i = 0; i < c.scan_ranges_sz; i++)
{
if (i > 0)
{
res += ",";
}
res += String(c.scan_ranges[i].start_khz);
int s = c.scan_ranges[i].end_khz - c.scan_ranges[i].start_khz;
if (s > 0)
{
res += ".." + String(c.scan_ranges[i].end_khz);
if (c.scan_ranges[i].step_khz < s)
{
res += ":" + String(c.scan_ranges[i].step_khz);
}
}
}
}
return res;
}
// findSepa looks for a sepa in s from position begin, and does two things:
// updates end with the index of the end of the string between begin and
// separator or end of string, and returns index of the next search position
// or -1 to stop the search.
//
// So you can do something like this:
// for (int i = 0, j = 0, k = 0; (i = findSepa(s, sepa, j, k)) >= 0; j = i)
// ...s.substring(j, k)
int findSepa(String s, String sepa, int begin, int &end)
{
int i = s.indexOf(sepa, begin);
if (i < 0)
{
end = s.length();
return begin == end ? -1 : end;
}
end = i;
return i + sepa.length();
}
uint64_t toUint64(String s)
{
String v = s.substring(0, s.length() % 15);
s = s.substring(v.length());
uint64_t r = v.toInt();
while (s.length() > 0)
{
r = r * ((uint64_t)1000000000000000ll) + s.substring(0, 15).toInt();
s = s.substring(15);
}
return r;
}
ScanRange parseScanRange(String &cfg, int &begin)
{
ScanRange res;
int end;
int i = findSepa(cfg, ",", begin, end);
String r = cfg.substring(begin, end);
begin = i;
if (i < 0)
{
res.start_khz = -1;
res.end_khz = -1;
res.step_khz = -1;
return res;
}
i = r.indexOf("..");
if (i < 0)
{
i = r.length();
}
res.start_khz = toUint64(r.substring(0, i));
if (i == r.length())
{
res.end_khz = res.start_khz;
res.step_khz = 0;
return res;
}
r = r.substring(i + 2);
i = r.indexOf("+");
if (i < 0)
{
i = r.indexOf("/");
if (i < 0)
i = r.length();
}
res.end_khz = toUint64(r.substring(0, i));
if (i == r.length())
{
res.step_khz = res.end_khz - res.start_khz;
}
else
{
res.step_khz = toUint64(r.substring(i + 1));
if (r.charAt(i) == '/')
{
// then it is not a literal increment, it is the number of steps
if (res.step_khz == 0)
res.step_khz = 1;
res.step_khz = round(((double)(res.end_khz - res.start_khz)) / res.step_khz);
}
}
return res;
}
void Config::configureDetectionStrategy(String cfg)
{
if (scan_ranges_sz > 0)
delete[] scan_ranges;
scan_ranges = NULL;
String method = cfg;
int i = cfg.indexOf(":");
if (i >= 0)
{
method = cfg.substring(0, i);
cfg = cfg.substring(i + 1);
}
else
{
cfg = "";
}
samples = 0;
i = method.indexOf(",");
if (i >= 0)
{
samples = method.substring(i + 1).toInt();
method = method.substring(0, i);
}
detection_strategy = method;
scan_ranges_sz = 0;
for (int i = 0, k = 0; (i = findSepa(cfg, ",", i, k)) >= 0; scan_ranges_sz++)
;
if (scan_ranges_sz == 0)
{
return;
}
scan_ranges = new ScanRange[scan_ranges_sz];
for (int i = 0, j = 0; i < scan_ranges_sz; i++)
{
scan_ranges[i] = parseScanRange(cfg, j);
}
}
bool Config::write_config(const char *path)
{
File f = SD.open(path, FILE_WRITE, /*create = */ true);
if (!f)
{
return false;
}
f.println("print_profile_time = " + String(print_profile_time ? "true" : "false"));
f.println("log_data_json_interval = " + String(log_data_json_interval));
f.println("listen_on_serial0 = " + listen_on_serial0);
f.println("listen_on_serial1 = " + listen_on_serial1);
f.println("listen_on_usb = " + listen_on_usb);
f.println("detection_strategy = " + detectionStrategyToStr(*this));
f.close();
return true;
}
ParseResult parse_config_line(String ln)
{
ln.trim();
if (ln.length() == 0 || ln.charAt(0) == '#')
{
// blank line or comment - skip
return ParseResult(String(), String());
}
int i = ln.indexOf("="); // ok, this must exist
if (i < 0)
{
return ParseResult(String("Broken config: expected '='"));
}
String k = ln.substring(0, i);
k.trim();
String v = ln.substring(i + 1);
v.trim();
if (v.length() == 0 || v.charAt(0) == '#')
{
return ParseResult(String("Broken config: expected non-empty value"));
}
if (v.charAt(0) == '"')
{
// quoted strings get special treatment
int i = v.indexOf('"', 1);
while (i > 0)
{
if (v.length() == i + 1 || v.charAt(i + 1) != '"')
break;
v = v.substring(0, i + 1) + v.substring(i + 2);
i = v.indexOf('"', i + 1);
}
if (i < 0)
{
return ParseResult(String("Broken config: expected closing quotes"));
}
String c = v.substring(i + 1);
c.trim();
if (c.length() > 0 && c.charAt(0) != '#')
{
return ParseResult(
String("Broken config: expected nothing but whitespace and "
"comments after value"));
}
v = v.substring(1, i);
}
else
{
int i = v.indexOf('#');
if (i > 0)
{
v = v.substring(0, i);
v.trim();
}
}
return ParseResult(k, v);
}