diff --git a/lib/config/config.cpp b/lib/config/config.cpp new file mode 100644 index 0000000..d35cfda --- /dev/null +++ b/lib/config/config.cpp @@ -0,0 +1,196 @@ +#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(); + } + + Serial.printf("Unknown key '%s' will be ignored\n", r.key); + } + + f.close(); + return c; +} + +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.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); +} diff --git a/lib/config/config.h b/lib/config/config.h new file mode 100644 index 0000000..0e24043 --- /dev/null +++ b/lib/config/config.h @@ -0,0 +1,32 @@ +#ifndef _LORA_CONFIG_H +#define _LORA_CONFIG_H + +#include + +#define CREATE_MISSING_CONFIG true +struct Config +{ + bool create_missing_config; + bool print_profile_time; + int log_data_json_interval; + + Config() + : create_missing_config(CREATE_MISSING_CONFIG), print_profile_time(false), + log_data_json_interval(1000) {}; + bool write_config(const char *path); + + static Config init(); +}; + +struct ParseResult +{ + String key; + String value; + String error; + + ParseResult(String e) : key(String()), value(String()), error(e) {}; + ParseResult(String k, String v) : key(k), value(v), error(String()) {}; +}; + +ParseResult parse_config_line(String ln); +#endif diff --git a/lib/loraboards/LoRaBoards.cpp b/lib/loraboards/LoRaBoards.cpp index 0331489..5afdb70 100644 --- a/lib/loraboards/LoRaBoards.cpp +++ b/lib/loraboards/LoRaBoards.cpp @@ -705,7 +705,33 @@ void setupBoards(bool disable_u8g2) beginPower(); - beginSDCard(); + bool sdReady; + for (int i = 0; i < 5 && !(sdReady = beginSDCard()); i++) + { + Serial.println("SD card failed or not found"); + delay(1000); + } + + if (sdReady) + { + char *card_type = "UNKNOWN"; + sdcard_type_t t = SD.cardType(); + + if (t == sdcard_type_t::CARD_MMC) + { + card_type = "MMC"; + } + else if (t == sdcard_type_t::CARD_SD) + { + card_type = "SD"; + } + else if (t == sdcard_type_t::CARD_SDHC) + { + card_type = "SDHC"; + } + + Serial.printf("SD card %s is ready.\n", card_type); + } if (!disable_u8g2) { diff --git a/src/main.cpp b/src/main.cpp index 1d96db0..7aac9af 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ #define RADIOLIB_GODMODE (1) #include +#include #include #include @@ -204,8 +205,8 @@ uint64_t drone_detected_frequency_end = 0; bool single_page_scan = false; // #define PRINT_DEBUG -#define PRINT_PROFILE_TIME +#define PRINT_PROFILE_TIME #ifdef PRINT_PROFILE_TIME uint64_t loop_start = 0; uint64_t loop_time = 0; @@ -215,7 +216,6 @@ uint64_t scan_start_time = 0; // log data via serial console, JSON format: // #define LOG_DATA_JSON true -int LOG_DATA_JSON_INTERVAL = 1000; // Log at least every second uint64_t x, y, range_item, w = WATERFALL_START, i = 0; int osd_x = 1, osd_y = 2, col = 0, max_bin = 32; @@ -360,6 +360,8 @@ void osdProcess() } #endif +Config config; + struct RadioScan : Scan { float getRSSI() override; @@ -514,7 +516,7 @@ void logToSerialTask(void *parameter) for (;;) { - ulTaskNotifyTake(true, pdMS_TO_TICKS(LOG_DATA_JSON_INTERVAL)); + ulTaskNotifyTake(true, pdMS_TO_TICKS(config.log_data_json_interval)); if (frequency_scan_result.begin != frequency_scan_result.end || frequency_scan_result.last_epoch != last_epoch) { @@ -551,7 +553,7 @@ void setup(void) #ifdef LILYGO setupBoards(); // true for disable U8g2 display library delay(500); - Serial.println("Setup LiLyGO board is done"); + Serial.println("Setup LiLybeginSDCardGO board is done"); #endif // LED brightness @@ -576,6 +578,8 @@ void setup(void) bt_start = millis(); wf_start = millis(); + config = Config::init(); + pinMode(LED, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); pinMode(REB_PIN, OUTPUT); @@ -938,11 +942,12 @@ void loop(void) drone_detected_frequency_start = 0; ranges_count = 0; -// reset scan time -#ifdef PRINT_PROFILE_TIME - scan_time = 0; - loop_start = millis(); -#endif + // reset scan time + if (config.print_profile_time) + { + scan_time = 0; + loop_start = millis(); + } r.epoch++; if (!ANIMATED_RELOAD || !single_page_scan) @@ -1337,10 +1342,13 @@ void loop(void) joy_btn_clicked = false; + if (config.print_profile_time) + { #ifdef PRINT_PROFILE_TIME - loop_time = millis() - loop_start; - Serial.printf("LOOP: %lld ms; SCAN: %lld ms;\n ", loop_time, scan_time); + loop_time = millis() - loop_start; + Serial.printf("LOOP: %lld ms; SCAN: %lld ms;\n ", loop_time, scan_time); #endif + } // No WiFi and BT Scan Without OSD #ifdef OSD_ENABLED #ifdef WIFI_SCANNING_ENABLED