diff --git a/src/config/Config.h b/src/config/Config.h index 286df73..d114b34 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -61,6 +61,23 @@ #define SCREEN_OFF_TIMEOUT_MS 60000 #define SCREEN_DIM_BRIGHTNESS 64 +// --- Radio Regions --- +enum RadioRegion : uint8_t { + REGION_AMERICAS = 0, // 915 MHz (902-928 ISM) + REGION_EUROPE = 1, // 868 MHz (863-870) + REGION_AUSTRALIA = 2, // 915 MHz (915-928) + REGION_ASIA = 3, // 923 MHz (AS923) + REGION_COUNT = 4 +}; + +static constexpr uint32_t REGION_FREQ[REGION_COUNT] = { + 915000000, 868000000, 915000000, 923000000 +}; + +static const char* const REGION_LABELS[REGION_COUNT] = { + "Americas (915)", "Europe (868)", "Australia (915)", "Asia (923)" +}; + // --- Serial Debug --- #define SERIAL_BAUD 115200 diff --git a/src/config/UserConfig.cpp b/src/config/UserConfig.cpp index 75e7523..43e41aa 100644 --- a/src/config/UserConfig.cpp +++ b/src/config/UserConfig.cpp @@ -11,6 +11,7 @@ bool UserConfig::parseJson(const String& json) { return false; } + _settings.radioRegion = constrain((int)(doc["radio_region"] | 0), 0, REGION_COUNT - 1); _settings.loraFrequency = doc["lora_freq"] | (long)LORA_DEFAULT_FREQ; _settings.loraSF = doc["lora_sf"] | (int)LORA_DEFAULT_SF; _settings.loraBW = doc["lora_bw"] | (long)LORA_DEFAULT_BW; @@ -75,6 +76,7 @@ bool UserConfig::parseJson(const String& json) { String UserConfig::serializeToJson() const { JsonDocument doc; + doc["radio_region"] = _settings.radioRegion; doc["lora_freq"] = _settings.loraFrequency; doc["lora_sf"] = _settings.loraSF; doc["lora_bw"] = _settings.loraBW; diff --git a/src/config/UserConfig.h b/src/config/UserConfig.h index 80daafc..186d634 100644 --- a/src/config/UserConfig.h +++ b/src/config/UserConfig.h @@ -18,6 +18,7 @@ struct TCPEndpoint { struct UserSettings { // Radio + uint8_t radioRegion = REGION_AMERICAS; uint32_t loraFrequency = LORA_DEFAULT_FREQ; uint8_t loraSF = LORA_DEFAULT_SF; uint32_t loraBW = LORA_DEFAULT_BW; diff --git a/src/hal/Power.cpp b/src/hal/Power.cpp index f379450..2724fe4 100644 --- a/src/hal/Power.cpp +++ b/src/hal/Power.cpp @@ -91,6 +91,8 @@ void Power::loop() { void Power::setState(State newState) { if (newState == _state) return; + const char* names[] = {"ACTIVE", "DIMMED", "SCREEN_OFF"}; + Serial.printf("[POWER] %s -> %s\n", names[_state], names[newState]); State oldState = _state; _state = newState; diff --git a/src/main.cpp b/src/main.cpp index 457b8bb..5d813ee 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -683,13 +683,12 @@ void setup() { ui.statusBar().setTransportMode("RatDeck"); ui.lvStatusBar().setTransportMode("RatDeck"); - // Keep UI alive during blocking radio TX (endPacket wait loop) + // Keep LVGL responsive during blocking radio operations (if screen is on) // Re-entrancy guard prevents nested lv_timer_handler() calls radio.setYieldCallback([]() { static bool inYield = false; if (inYield) return; inYield = true; - powerMgr.activity(); // Keep screen alive during TX if (powerMgr.isScreenOn()) { lv_timer_handler(); } @@ -847,6 +846,15 @@ void setup() { gps.setPosixTZ(tz); } #endif + // Warn if timezone suggests a different radio region + uint8_t tzRegion = TIMEZONE_TABLE[tzIdx].radioRegion; + if (tzRegion != userConfig.settings().radioRegion) { + char msg[64]; + snprintf(msg, sizeof(msg), "TZ suggests %s region", REGION_LABELS[tzRegion]); + ui.lvStatusBar().showToast(msg, 3000); + Serial.printf("[REGION] Timezone suggests %s, current is %s\n", + REGION_LABELS[tzRegion], REGION_LABELS[userConfig.settings().radioRegion]); + } goHome(); }); diff --git a/src/ui/screens/LvSettingsScreen.cpp b/src/ui/screens/LvSettingsScreen.cpp index d596e56..a845433 100644 --- a/src/ui/screens/LvSettingsScreen.cpp +++ b/src/ui/screens/LvSettingsScreen.cpp @@ -47,6 +47,7 @@ void LvSettingsScreen::applyPreset(int presetIdx) { const auto& p = LV_PRESETS[presetIdx]; s.loraSF = p.sf; s.loraBW = p.bw; s.loraCR = p.cr; s.loraTxPower = p.txPower; s.loraPreamble = p.preamble; + s.loraFrequency = REGION_FREQ[constrain(s.radioRegion, 0, REGION_COUNT - 1)]; } bool LvSettingsScreen::isEditable(int idx) const { @@ -230,6 +231,20 @@ void LvSettingsScreen::buildItems() { // Radio int radioStart = idx; { + // Region picker — always visible + SettingItem regionItem; + regionItem.label = "Region"; + regionItem.type = SettingType::ENUM_CHOICE; + regionItem.getter = [&s]() { return (int)s.radioRegion; }; + regionItem.setter = [this, &s](int v) { + s.radioRegion = constrain(v, 0, REGION_COUNT - 1); + if (_ui) _ui->lvStatusBar().showToast("Select a preset to apply frequency", 2500); + }; + regionItem.minVal = 0; regionItem.maxVal = REGION_COUNT - 1; regionItem.step = 1; + regionItem.enumLabels = {REGION_LABELS[0], REGION_LABELS[1], REGION_LABELS[2], REGION_LABELS[3]}; + _items.push_back(regionItem); + idx++; + SettingItem presetItem; presetItem.label = "Preset"; presetItem.type = SettingType::ENUM_CHOICE; @@ -243,13 +258,25 @@ void LvSettingsScreen::buildItems() { // Custom radio parameters — only visible in Developer Mode if (s.devMode) { _items.push_back({"Frequency", SettingType::INTEGER, - [&s]() { return (int)(s.loraFrequency / 1000); }, - [&s](int v) { s.loraFrequency = (uint32_t)v * 1000; }, + [&s]() { return (int)(s.loraFrequency); }, + [&s](int v) { s.loraFrequency = (uint32_t)v; }, [](int v) -> String { - char buf[16]; snprintf(buf, sizeof(buf), "%d.%03d MHz", v / 1000, v % 1000); + char buf[20]; + int mhz = v / 1000000; + int rem = v % 1000000; + if (rem == 0) { + snprintf(buf, sizeof(buf), "%d MHz", mhz); + } else { + char frac[8]; + snprintf(frac, sizeof(frac), "%06d", rem); + int len = 6; + while (len > 0 && frac[len - 1] == '0') len--; + frac[len] = '\0'; + snprintf(buf, sizeof(buf), "%d.%s MHz", mhz, frac); + } return String(buf); }, - 137000, 1020000, 125}); + 137000000, 1020000000, 125000}); idx++; _items.push_back({"TX Power", SettingType::INTEGER, [&s]() { return s.loraTxPower; }, [&s](int v) { s.loraTxPower = v; }, @@ -282,7 +309,14 @@ void LvSettingsScreen::buildItems() { idx++; } _categories.push_back({"Radio", radioStart, idx - radioStart, - [this]() { int p = detectPreset(); return (p >= 0) ? String(LV_PRESETS[p].name) : String("Custom"); }}); + [this]() { + int p = detectPreset(); + auto& s = _cfg->settings(); + String label = (p >= 0) ? String(LV_PRESETS[p].name) : String("Custom"); + label += " "; + label += REGION_LABELS[constrain(s.radioRegion, 0, REGION_COUNT - 1)]; + return label; + }}); // Network int netStart = idx; diff --git a/src/ui/screens/LvTimezoneScreen.h b/src/ui/screens/LvTimezoneScreen.h index 1ec0670..83d6d2d 100644 --- a/src/ui/screens/LvTimezoneScreen.h +++ b/src/ui/screens/LvTimezoneScreen.h @@ -1,37 +1,39 @@ #pragma once #include "ui/UIManager.h" +#include "config/Config.h" #include struct TimezoneEntry { const char* label; // Display name (e.g., "New York (EST/EDT)") const char* posixTZ; // POSIX TZ string for setenv("TZ", ...) int8_t baseOffset; // Base UTC offset for display (e.g., -5 for EST) + uint8_t radioRegion; // Mapped RadioRegion for ISM band defaults }; // Major world timezones with DST rules where applicable static const TimezoneEntry TIMEZONE_TABLE[] = { - {"Honolulu", "HST10", -10}, - {"Anchorage", "AKST9AKDT,M3.2.0,M11.1.0", -9}, - {"Los Angeles", "PST8PDT,M3.2.0,M11.1.0", -8}, - {"Denver", "MST7MDT,M3.2.0,M11.1.0", -7}, - {"Phoenix", "MST7", -7}, - {"Chicago", "CST6CDT,M3.2.0,M11.1.0", -6}, - {"New York", "EST5EDT,M3.2.0,M11.1.0", -5}, - {"San Juan", "AST4", -4}, - {"Sao Paulo", "<-03>3", -3}, - {"London", "GMT0BST,M3.5.0/1,M10.5.0", 0}, - {"Paris", "CET-1CEST,M3.5.0,M10.5.0/3", 1}, - {"Cairo", "EET-2", 2}, - {"Moscow", "MSK-3", 3}, - {"Dubai", "<+04>-4", 4}, - {"Karachi", "PKT-5", 5}, - {"Kolkata", "IST-5:30", 5}, - {"Bangkok", "<+07>-7", 7}, - {"Singapore", "<+08>-8", 8}, - {"Tokyo", "JST-9", 9}, - {"Sydney", "AEST-10AEDT,M10.1.0,M4.1.0/3", 10}, - {"Auckland", "NZST-12NZDT,M9.5.0,M4.1.0/3", 12}, + {"Honolulu", "HST10", -10, REGION_AMERICAS}, + {"Anchorage", "AKST9AKDT,M3.2.0,M11.1.0", -9, REGION_AMERICAS}, + {"Los Angeles", "PST8PDT,M3.2.0,M11.1.0", -8, REGION_AMERICAS}, + {"Denver", "MST7MDT,M3.2.0,M11.1.0", -7, REGION_AMERICAS}, + {"Phoenix", "MST7", -7, REGION_AMERICAS}, + {"Chicago", "CST6CDT,M3.2.0,M11.1.0", -6, REGION_AMERICAS}, + {"New York", "EST5EDT,M3.2.0,M11.1.0", -5, REGION_AMERICAS}, + {"San Juan", "AST4", -4, REGION_AMERICAS}, + {"Sao Paulo", "<-03>3", -3, REGION_AMERICAS}, + {"London", "GMT0BST,M3.5.0/1,M10.5.0", 0, REGION_EUROPE}, + {"Paris", "CET-1CEST,M3.5.0,M10.5.0/3", 1, REGION_EUROPE}, + {"Cairo", "EET-2", 2, REGION_EUROPE}, + {"Moscow", "MSK-3", 3, REGION_EUROPE}, + {"Dubai", "<+04>-4", 4, REGION_EUROPE}, + {"Karachi", "PKT-5", 5, REGION_EUROPE}, + {"Kolkata", "IST-5:30", 5, REGION_EUROPE}, + {"Bangkok", "<+07>-7", 7, REGION_ASIA}, + {"Singapore", "<+08>-8", 8, REGION_ASIA}, + {"Tokyo", "JST-9", 9, REGION_ASIA}, + {"Sydney", "AEST-10AEDT,M10.1.0,M4.1.0/3", 10, REGION_AUSTRALIA}, + {"Auckland", "NZST-12NZDT,M9.5.0,M4.1.0/3", 12, REGION_AUSTRALIA}, }; static constexpr int TIMEZONE_COUNT = sizeof(TIMEZONE_TABLE) / sizeof(TIMEZONE_TABLE[0]);