Files
pyxis/lib/tdeck_ui/UI/LXMF/SettingsScreen.h
T
torlando-agent[bot]andClaude Opus 4.8 36d93f965f fix: announce/sync interval units+defaults, live Settings clock, OTA repair
Settings/clock (user-reported):
- Announce interval: default 1h (was 60s); the field is now MINUTES (label
  "Announce Interval (min):", populate /60, read *60).
- Prop sync interval: default 4h (was 1h); the field is now HOURS (label
  "Prop Sync Interval (hrs):", populate /3600, read *3600); log prints hours.
- Settings clock now ticks live: SettingsScreen::tick() (throttled ~1s) is hooked
  into UIManager::update() like the announce-list tick, so the Time/GPS/system
  readouts refresh while the screen is open instead of being a static snapshot.

OTA repair (was broken two ways; diagnosed via an ultracode workflow):
- patch_nimble.py hardcoded the `tdeck` libdeps path, so building any OTHER env
  (tdeck-ota gets its own libdeps tree) never received the NimBLE patch that
  DEFINES nimble_host_reset_reason -> undefined-reference link error. Made it
  env-aware (env.get("PIOENV",...)), matching the four sibling pre-scripts. Also
  restores 3 NimBLE stability patches that were silently missing from OTA builds.
- ArduinoOTA onStart synchronously called TCPClientInterface::stop() (blocks up to
  30s), overrunning espota's ~10s connect-back window -> "No response from device"
  (phase 2), and its un-fed watchdog tripped reboots. Made onStart non-blocking.
- [env:tdeck-ota] upload_command invoked a non-existent tools/espota.py -> switched
  to upload_protocol=espota.
- Do NOT WiFi.setSleep(false): the ESP32 requires WiFi modem-sleep when WiFi+BT
  coexist (aborts at boot otherwise) -- documented inline.

Verified on device: tdeck-ota links; OTA over WiFi completes 100% (Result: OK) and
the device reboots clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
2026-06-22 23:28:48 -04:00

351 lines
10 KiB
C++

// Copyright (c) 2024 microReticulum contributors
// SPDX-License-Identifier: MIT
#ifndef UI_LXMF_SETTINGSSCREEN_H
#define UI_LXMF_SETTINGSSCREEN_H
#ifdef ARDUINO
#include <Arduino.h>
#include <lvgl.h>
#include <Preferences.h>
#include <functional>
#include <microReticulum/Bytes.h>
#include <microReticulum/Identity.h>
// Forward declaration
class TinyGPSPlus;
namespace UI {
namespace LXMF {
/**
* Application settings structure
*/
struct AppSettings {
// Network
String wifi_ssid;
String wifi_password;
String tcp_host;
uint16_t tcp_port;
// Identity
String display_name;
// Display
uint8_t brightness;
uint16_t screen_timeout; // seconds, 0 = never
bool keyboard_light; // Enable keyboard backlight on keypress
// Notifications
bool notification_sound; // Play sound on message received
uint8_t notification_volume; // Volume 0-100
// Interfaces
bool tcp_enabled;
bool lora_enabled;
float lora_frequency; // MHz
float lora_bandwidth; // kHz
uint8_t lora_sf; // Spreading factor (7-12)
uint8_t lora_cr; // Coding rate (5-8)
int8_t lora_power; // TX power dBm (2-22)
bool auto_enabled; // Enable AutoInterface (WiFi peer discovery)
bool ble_enabled; // Enable BLE mesh interface
// Advanced
uint32_t announce_interval; // seconds (UI shows minutes; default 3600 = 1h)
uint32_t sync_interval; // seconds (0 = disabled; UI shows hours; default 14400 = 4h)
bool gps_time_sync;
// Propagation
bool prop_auto_select; // Auto-select best propagation node
String prop_selected_node; // Hex string of selected node hash
bool prop_fallback_enabled; // Fall back to propagation on direct failure
bool prop_only; // Only send via propagation (no direct/opportunistic)
// Defaults
AppSettings() :
tcp_host("sideband.connect.reticulum.network"),
tcp_port(4965),
brightness(180),
screen_timeout(60),
keyboard_light(false),
notification_sound(true),
notification_volume(10),
tcp_enabled(true),
lora_enabled(false),
lora_frequency(927.25f),
lora_bandwidth(62.5f),
lora_sf(7),
lora_cr(5),
lora_power(17),
auto_enabled(false),
ble_enabled(false),
announce_interval(3600),
sync_interval(14400),
gps_time_sync(true),
prop_auto_select(true),
prop_selected_node(""),
prop_fallback_enabled(true),
prop_only(false)
{}
};
/**
* Settings Screen
*
* Allows configuration of WiFi, TCP server, display, and other settings.
* Also shows GPS status and system info.
*
* Layout:
* +---------------------------------------+
* | [<] Settings [Save] | 36px
* +---------------------------------------+
* | == Network == |
* | WiFi SSID: [__________________] |
* | Password: [******************] |
* | TCP Server: [_________________] |
* | TCP Port: [____] [Reconnect] |
* | |
* | == Identity == |
* | Display Name: [_______________] |
* | |
* | == Display == |
* | Brightness: [=======o------] 180 |
* | Timeout: [1 min v] |
* | |
* | == GPS Status == |
* | Satellites: 8 |
* | Location: 40.7128, -74.0060 |
* | Altitude: 10.5m |
* | HDOP: 1.2 (Excellent) |
* | |
* | == System Info == |
* | Identity: a1b2c3d4e5f6... |
* | LXMF: f7e8d9c0b1a2... |
* | Firmware: v1.0.0 |
* | Storage: 1.2 MB free |
* | RAM: 145 KB free |
* | |
* | == Advanced == |
* | Announce: [60] seconds |
* | GPS Sync: [ON] |
* +---------------------------------------+
*/
class SettingsScreen {
public:
// Callback types
using BackCallback = std::function<void()>;
using SaveCallback = std::function<void(const AppSettings&)>;
using WifiReconnectCallback = std::function<void(const String&, const String&)>;
using BrightnessChangeCallback = std::function<void(uint8_t)>;
using PropagationNodesCallback = std::function<void()>;
/**
* Create settings screen
* @param parent Parent LVGL object
*/
SettingsScreen(lv_obj_t* parent = nullptr);
/**
* Destructor
*/
~SettingsScreen();
/**
* Load settings from NVS
*/
void load_settings();
/**
* Save settings to NVS
*/
void save_settings();
/**
* Get current settings
*/
const AppSettings& get_settings() const { return _settings; }
/**
* Set identity hash for display
*/
void set_identity_hash(const RNS::Bytes& hash);
/**
* Set LXMF delivery address hash
*/
void set_lxmf_address(const RNS::Bytes& hash);
/**
* Set GPS pointer for status display
*/
void set_gps(TinyGPSPlus* gps);
/**
* Refresh GPS and system info displays
*/
void refresh();
void tick(); // periodic update while the screen is shown (ticks clock/GPS/system readouts)
/**
* Set callback for back button
*/
void set_back_callback(BackCallback callback);
/**
* Set callback for save button
*/
void set_save_callback(SaveCallback callback);
/**
* Set callback for WiFi reconnect button
*/
void set_wifi_reconnect_callback(WifiReconnectCallback callback);
/**
* Set callback for brightness changes (immediate)
*/
void set_brightness_change_callback(BrightnessChangeCallback callback);
/**
* Set callback for propagation nodes button
*/
void set_propagation_nodes_callback(PropagationNodesCallback callback);
/**
* Show the screen
*/
void show();
/**
* Hide the screen
*/
void hide();
/**
* Get the root LVGL object
*/
lv_obj_t* get_object();
private:
// Main UI components
lv_obj_t* _screen;
lv_obj_t* _header;
lv_obj_t* _content;
lv_obj_t* _btn_back;
lv_obj_t* _btn_save;
// Network section inputs
lv_obj_t* _ta_wifi_ssid;
lv_obj_t* _ta_wifi_password;
lv_obj_t* _ta_tcp_host;
lv_obj_t* _ta_tcp_port;
lv_obj_t* _btn_reconnect;
// Identity section
lv_obj_t* _ta_display_name;
// Display section
lv_obj_t* _slider_brightness;
lv_obj_t* _label_brightness_value;
lv_obj_t* _switch_kb_light;
lv_obj_t* _dropdown_timeout;
// Notifications section
lv_obj_t* _switch_notification_sound;
lv_obj_t* _slider_notification_volume;
lv_obj_t* _label_notification_volume_value;
// GPS status labels (read-only)
lv_obj_t* _label_gps_sats;
lv_obj_t* _label_gps_coords;
lv_obj_t* _label_gps_alt;
lv_obj_t* _label_gps_hdop;
lv_obj_t* _label_gps_time;
uint32_t _last_tick_ms = 0; // throttle for tick()
// System info labels (read-only)
lv_obj_t* _label_identity_hash;
lv_obj_t* _label_lxmf_address;
lv_obj_t* _label_firmware;
lv_obj_t* _label_storage;
lv_obj_t* _label_ram;
// Interfaces section
lv_obj_t* _switch_tcp_enabled;
lv_obj_t* _switch_lora_enabled;
lv_obj_t* _ta_lora_frequency;
lv_obj_t* _dropdown_lora_bandwidth;
lv_obj_t* _dropdown_lora_sf;
lv_obj_t* _dropdown_lora_cr;
lv_obj_t* _slider_lora_power;
lv_obj_t* _label_lora_power_value;
lv_obj_t* _lora_params_container; // Container for LoRa params (shown/hidden based on enabled)
lv_obj_t* _switch_auto_enabled;
lv_obj_t* _switch_ble_enabled;
// Advanced section
lv_obj_t* _ta_announce_interval;
lv_obj_t* _ta_sync_interval;
lv_obj_t* _switch_gps_sync;
// Delivery/Propagation section
lv_obj_t* _btn_propagation_nodes;
lv_obj_t* _switch_prop_fallback;
lv_obj_t* _switch_prop_only;
// Data
AppSettings _settings;
RNS::Bytes _identity_hash;
RNS::Bytes _lxmf_address;
TinyGPSPlus* _gps;
// Callbacks
BackCallback _back_callback;
SaveCallback _save_callback;
WifiReconnectCallback _wifi_reconnect_callback;
BrightnessChangeCallback _brightness_change_callback;
PropagationNodesCallback _propagation_nodes_callback;
// UI construction
void create_header();
void create_content();
void create_network_section(lv_obj_t* parent);
void create_identity_section(lv_obj_t* parent);
void create_display_section(lv_obj_t* parent);
void create_notifications_section(lv_obj_t* parent);
void create_interfaces_section(lv_obj_t* parent);
void create_gps_section(lv_obj_t* parent);
void create_system_section(lv_obj_t* parent);
void create_advanced_section(lv_obj_t* parent);
void create_delivery_section(lv_obj_t* parent);
// Helpers
lv_obj_t* create_section_header(lv_obj_t* parent, const char* title);
lv_obj_t* create_label_row(lv_obj_t* parent, const char* label);
lv_obj_t* create_text_input(lv_obj_t* parent, const char* placeholder,
bool password = false, int max_len = 64);
// Update UI from settings
void update_ui_from_settings();
void update_settings_from_ui();
void update_gps_display();
void update_system_info();
// Event handlers
static void on_back_clicked(lv_event_t* event);
static void on_save_clicked(lv_event_t* event);
static void on_reconnect_clicked(lv_event_t* event);
static void on_brightness_changed(lv_event_t* event);
static void on_lora_enabled_changed(lv_event_t* event);
static void on_lora_power_changed(lv_event_t* event);
static void on_propagation_nodes_clicked(lv_event_t* event);
static void on_notification_volume_changed(lv_event_t* event);
};
} // namespace LXMF
} // namespace UI
#endif // ARDUINO
#endif // UI_LXMF_SETTINGSSCREEN_H