From 0502c68e20ac9fe6d1ea2d128cf1421842927664 Mon Sep 17 00:00:00 2001 From: liu weikai Date: Sat, 11 Jul 2026 17:23:14 +0800 Subject: [PATCH] Implement phone-independent mesh settings Add protocol-scoped MQTT/channel settings for Meshtastic and MeshCore, centralize Settings identity/visibility through SettingId specs, remove BLE from the ESP Arduino firmware profile, and keep Settings Wi-Fi scan/channel/backup paths on fixed buffers. --- CHANGELOG.md | Bin 141998 -> 145280 bytes docs/MULTI_PROTOCOL_SUPPORT.md | 55 +- modules/core_sys/include/app/app_config.h | 2 +- .../include/platform/ui/wifi_runtime.h | 4 +- .../settings/settings_channel_actions.h | 33 + .../ui/screens/settings/settings_spec.h | 59 + .../ui/screens/settings/settings_state.h | 154 ++- modules/ui_shared/library.json | 2 + .../ui_shared/src/ui/menu/menu_runtime.cpp | 4 + .../runtime_mesh_status_source.cpp | 12 +- .../settings/settings_channel_actions.cpp | 304 +++++ .../settings/settings_page_components.cpp | 1182 ++++++++--------- .../src/ui/screens/settings/settings_spec.cpp | 694 ++++++++++ modules/ui_shared/src/ui/ui_status.cpp | 21 +- .../platform/esp/arduino_common/app_tasks.h | 2 +- platform/esp/arduino_common/library.json | 1 + .../arduino_common/src/app_config_store.cpp | 6 +- .../esp/arduino_common/src/app_context.cpp | 9 + .../src/app_runtime_bootstrap_support.cpp | 4 +- .../src/app_runtime_support.cpp | 16 +- .../arduino_bt_disabled_stubs.cpp | 19 + .../src/ble_disabled/ble_manager_disabled.cpp | 56 + .../chat/infra/mesh_mqtt_client_runtime.cpp | 13 +- .../platform_ui_firmware_update_runtime.cpp | 15 +- .../platform_ui_settings_backup_runtime.cpp | 75 +- .../platform/esp/common/wifi_runtime_impl.h | 95 +- .../src/platform_ui_wifi_runtime.cpp | 91 +- .../common/src/platform/ui/wifi_runtime.cpp | 6 +- .../src/platform_ui_wifi_runtime.cpp | 6 +- platformio.ini | 3 +- 30 files changed, 2163 insertions(+), 780 deletions(-) create mode 100644 modules/ui_shared/include/ui/screens/settings/settings_channel_actions.h create mode 100644 modules/ui_shared/include/ui/screens/settings/settings_spec.h create mode 100644 modules/ui_shared/src/ui/screens/settings/settings_channel_actions.cpp create mode 100644 modules/ui_shared/src/ui/screens/settings/settings_spec.cpp create mode 100644 platform/esp/arduino_common/src/ble_disabled/arduino_bt_disabled_stubs.cpp create mode 100644 platform/esp/arduino_common/src/ble_disabled/ble_manager_disabled.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9e989228563de9492cd072f0cc770bb42064c0..0455e11ba74f721f8caeeebc6aa83671566dd68d 100644 GIT binary patch delta 2376 zcmb7FZERCj7(S=qHb(0j+HO>gyNgi}S9(KqCL}xXBNH&V;BjG2+A!z!|B}d+sjX3V%$~d(U~# zd*1WD&-0x3p8cWj;7r}QcDFe9kyqTDTD~O2Vl2o)Y=an|^oX0EH!j-E3^Ux~@Cc=O zv6#K;6`#2p8;000+s!hp5ARVH6t};BBA_z^^Ca8DbcCRBMvk_yD0>B#0vo~_!m&Z@ z7~kYEf8NZ!;>3v8W4S2Ul45zdWSJqR&$NgWlgrGz*Lh9D{CI;*7kyVewK=wiDTuV= zTYpnPJExy~_2SXm?KAroq(85S`cDtk%-_PWBF3-09UNKcou{EK!aD%=eyWiH(nRp|mEh0$(@S}*oaKYqT~op!8r zv3eH3LIfyffnz_)H&Fb3%+s)UvN(I0bxQC{iUckXM}=*HbC&Ibg`jSQC4_g54aji> zVbT&qTG@tCfW3q#%{s9!h4nd%rEMej7O|e1i$zJ$@gevTS$Wuv)iO77QE-r6p$Rjx zP+B^CQfxrTBhd(8?3qKv{lUDMu(TIvD%J_Nna*_L7IY?y~jucK8 zht~O@rrJnqlutjl3&v)1y<!IrBPWVu?6D_7^EN)hH^iLoKkc9u(BF3lP1ik>~X^|7Az z(Z#Xf4&5o{!WWMkC1=^CjfMAyR~3HMj})!4o?Teh?Mz*4QSS>q4%L>@24FzI4&w~E zYEnv4KubtKL}feWw-o()ma_Yx8fU}5jBA%?nYcSl!66gxLyePz5g=$hwPHxVV zx>&0@o#OrpgV&g`7KhtEU*e=hlDC1t=rEHWe-NW2EGa~%3vHzV`IzA@?h!w>I+rG~ zFegiKZDfV`tIaFcu4xs=ni@-UkYU>w$NtqG)@GuY-&(ozn~8v?Mh?6?HU9tb>F9Bm z{~1kB6h26r9grV2(S@aN>wfaUTCFB>HR>!Ed*9|salFYJt0+xjgUP8BR|yCz(~u}~ zbzzA!V@DEg`cKhzZ8rX4x2N=F7)m=H3O}RW(oaYD&ab)$;^!|dd(H8bt(yqq%;%~D z+!SD=ev`#>z?=zipV-*qHZ?bIG?NgEDwZ?e)PAUA zPOC(a zR$BTPIG3`B;W>bDmJN!Ak9{Sp+`s}TtUItUw%utd&M=C%@2q_iRZQp8s=c4?P6GeR zrkp$ zjZ%A}Es_ggf8U1RlC;9A`73?W6(4h!>5wD+v%T-7(i6@5PU6^V_Kq8Ttf2P6 nphgl*qt5|-h%6lG^L_1x=kR*po9li2!g`-$$Y-8h?Wp??@S-%+ delta 50 zcmZp8&#~?*#|A5b$%lNkCfivGZH^H3VPfQ(Tsup7^D1c #include -#include namespace platform::ui::wifi { @@ -57,7 +57,7 @@ bool find_saved_config(const char* ssid, Config& out); bool apply_enabled(bool enabled); bool connect(const Config* override_config = nullptr); void disconnect(); -bool scan(std::vector& out_results); +bool scan(ScanResult* out_results, std::size_t capacity, std::size_t& out_count); Status status(); } // namespace platform::ui::wifi diff --git a/modules/ui_shared/include/ui/screens/settings/settings_channel_actions.h b/modules/ui_shared/include/ui/screens/settings/settings_channel_actions.h new file mode 100644 index 00000000..40ed9385 --- /dev/null +++ b/modules/ui_shared/include/ui/screens/settings/settings_channel_actions.h @@ -0,0 +1,33 @@ +#pragma once + +#include "app/app_config.h" +#include "ui/screens/settings/settings_state.h" +#include +#include + +namespace settings::ui::channel +{ + +bool is_zero_key(const std::uint8_t* key, std::size_t len); +void bytes_to_hex(const std::uint8_t* data, std::size_t len, char* out, std::size_t out_len); +bool parse_psk(const char* text, + std::uint8_t* out, + std::size_t out_len, + std::size_t* parsed_len = nullptr); +void meshtastic_key_to_text(const std::uint8_t* key, + std::size_t key_capacity, + std::uint8_t stored_len, + char* out, + std::size_t out_len); +bool parse_meshtastic_key_text(const char* text, + std::uint8_t* key, + std::size_t key_capacity, + std::uint8_t* out_key_len); + +void sync_meshtastic_channel_fields(const app::AppConfig& config, SettingsData& settings); +bool generate_meshtastic_channel_key(app::AppConfig& config, + SettingsData& settings, + bool primary); +bool generate_meshcore_channel_key(app::AppConfig& config, SettingsData& settings); + +} // namespace settings::ui::channel diff --git a/modules/ui_shared/include/ui/screens/settings/settings_spec.h b/modules/ui_shared/include/ui/screens/settings/settings_spec.h new file mode 100644 index 00000000..a59c0a2e --- /dev/null +++ b/modules/ui_shared/include/ui/screens/settings/settings_spec.h @@ -0,0 +1,59 @@ +#pragma once + +#include "chat/domain/chat_types.h" +#include "ui/screens/settings/settings_state.h" +#include +#include + +namespace settings::ui::spec +{ + +enum class DynamicOptionKind : std::uint8_t +{ + None, + ChatRegion, + MeshCoreRegionPreset, + Locale, + TimeZone, + WifiNetwork, + TxPower, +}; + +struct VisibilityContext +{ + chat::MeshProtocol protocol = chat::MeshProtocol::Meshtastic; + bool wifi_supported = false; + bool has_wifi_networks = false; + bool firmware_update_supported = false; + bool settings_backup_supported = false; + bool wireless_companion_supported = false; + bool mt_secondary_enabled = false; + bool mt_use_preset = false; + bool reticulum_wifi_visible = false; + bool reticulum_lora_visible = false; + bool screen_brightness_supported = false; + bool screen_timeout_supported = false; + bool gps_baud_supported = false; + bool gps_init_policy_supported = false; + bool gps_gnss_supported = false; + bool gps_interval_supported = false; + bool gps_alt_ref_supported = false; + bool gps_coord_format_supported = false; + bool external_nmea_supported = false; + bool battery_gauge_supported = false; +}; + +SettingId id_for_key(const char* pref_key); +const char* key_for_id(SettingId id); + +void bind_item(SettingItem& item); +void bind_items(SettingItem* items, std::size_t count); + +DynamicOptionKind dynamic_option_kind(SettingId id); +bool option_labels_are_translated(SettingId id); +bool option_labels_use_content_font(SettingId id); +bool is_settings_store_owned_enum(SettingId id); +bool is_settings_store_owned_toggle(SettingId id); +bool should_show(SettingId id, const VisibilityContext& context); + +} // namespace settings::ui::spec diff --git a/modules/ui_shared/include/ui/screens/settings/settings_state.h b/modules/ui_shared/include/ui/screens/settings/settings_state.h index 1332f5fa..d6bb6741 100644 --- a/modules/ui_shared/include/ui/screens/settings/settings_state.h +++ b/modules/ui_shared/include/ui/screens/settings/settings_state.h @@ -8,6 +8,7 @@ #include "lvgl.h" #include "ui/widgets/top_bar.h" #include +#include namespace settings::ui { @@ -21,6 +22,145 @@ enum class SettingType Action, }; +enum class SettingId : std::uint16_t +{ + Unknown, + ChatUser, + ChatShort, + MeshProtocol, + ChatMessageAlerts, + ChatContactAlerts, + ChatRegion, + ChatChannel, + ChatPsk, + MtPrimaryEnabled, + MtPrimaryName, + MtPrimaryKey, + MtPrimaryKeyGenerate, + MtPrimaryUplink, + MtPrimaryDownlink, + MtSecondaryEnabled, + MtSecondaryName, + MtSecondaryKey, + MtSecondaryKeyGenerate, + MtSecondaryUplink, + MtSecondaryDownlink, + PrivacyEncrypt, + MtMqttEnabled, + MtMqttHost, + MtMqttPort, + MtMqttRoot, + MtMqttUser, + MtMqttPass, + MtMqttUplink, + MtMqttDownlink, + McMqttEnabled, + McMqttHost, + McMqttPort, + McMqttRoot, + McMqttUser, + McMqttPass, + McMqttUplink, + McMqttDownlink, + McChannelSlot, + McChannelName, + McChannelKey, + McChannelKeyGenerate, + RtBearer, + RtLoraEnabled, + RtDisplayName, + RtIdentityHash, + RtLxmfAddress, + RtWifiGateway, + RtWifiHost, + RtWifiPort, + RtWifiAuto, + RtAnonymousPeer, + NetUsePreset, + NetPreset, + NetBw, + NetSf, + NetCr, + NetTxPower, + NetHopLimit, + NetTxEnabled, + NetOverrideDuty, + NetChannelNum, + NetFreqOffset, + NetOverrideFreq, + NetRelay, + NetDutyCycle, + NetUtil, + McRegionPreset, + McFreq, + McBw, + McSf, + McCr, + McTxPower, + McRepeat, + McRxDelay, + McAirtime, + McFloodMax, + McMultiAcks, + McSendProfile, + McForwardProfile, + GpsEnabled, + GpsInitBaud, + GpsInitProbeMs, + GpsInitProfile, + GpsInitRxm, + GpsInitGnss, + GpsInitNmea, + GpsMode, + GpsSatMask, + GpsStrategy, + GpsInterval, + GpsAltRef, + GpsCoordFmt, + ExternalNmea, + ExternalNmeaSent, + GpsDiagnostics, + MapCoord, + MapSource, + MapContour, + MapTrack, + MapTrackInterval, + MapTrackFormat, + DisplayLocale, + EnabledImes, + ScreenTimeout, + ScreenBrightness, + SpeakerVolume, + VibrationEnabled, + C6CompanionStatus, + C6EnterDownload, + TimezoneProfile, + ManualTimeSet, + GaugeDesignMah, + GaugeFullMah, + WifiEnabled, + WifiStatus, + WifiScan, + WifiNetwork, + WifiSsid, + WifiPassword, + WifiConnect, + WifiDisconnect, + FwCurrent, + FwLatest, + FwStatus, + FwCheck, + FwInstall, + SettingsBackupStatus, + SettingsBackup, + SettingsRestore, + AdvDebug, + ChatResetMesh, + ChatResetNodes, + ChatClearMessages, + SystemFactoryReset, +}; + struct SettingOption { const char* label; @@ -39,6 +179,7 @@ struct SettingItem size_t text_max; bool mask_text; const char* pref_key; + SettingId id = SettingId::Unknown; }; struct SettingsData @@ -76,6 +217,18 @@ struct SettingsData int chat_message_alerts = 1; int chat_contact_alerts = 1; + // Meshtastic channels + bool mt_primary_enabled = true; + char mt_primary_name[32] = "LongFast"; + char mt_primary_key[65] = {}; + bool mt_primary_uplink = false; + bool mt_primary_downlink = false; + bool mt_secondary_enabled = false; + char mt_secondary_name[32] = "Secondary"; + char mt_secondary_key[65] = {}; + bool mt_secondary_uplink = false; + bool mt_secondary_downlink = false; + // Network int net_use_preset = 1; int net_modem_preset = 0; @@ -158,7 +311,6 @@ struct SettingsData int speaker_volume = 45; int display_locale_index = 0; char c6_companion_status[96] = ""; - bool ble_enabled = true; bool vibration_enabled = true; // Wi-Fi diff --git a/modules/ui_shared/library.json b/modules/ui_shared/library.json index c8e93f37..6e576cd5 100644 --- a/modules/ui_shared/library.json +++ b/modules/ui_shared/library.json @@ -6,6 +6,8 @@ "srcDir": "src", "srcFilter": [ "+<*>", + "-", + "-", "-" ], "flags": [ diff --git a/modules/ui_shared/src/ui/menu/menu_runtime.cpp b/modules/ui_shared/src/ui/menu/menu_runtime.cpp index 0e4803a8..4bc0b069 100644 --- a/modules/ui_shared/src/ui/menu/menu_runtime.cpp +++ b/modules/ui_shared/src/ui/menu/menu_runtime.cpp @@ -690,7 +690,9 @@ void createTopBar() menu_wifi_icon = lv_image_create(menu_status_row); menu_team_icon = lv_image_create(menu_status_row); menu_msg_icon = lv_image_create(menu_status_row); +#if defined(TRAIL_MATE_ENABLE_BLE) && TRAIL_MATE_ENABLE_BLE menu_ble_icon = lv_image_create(menu_status_row); +#endif lv_obj_add_flag(menu_radio_mod_icon, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(menu_walkie_monitor_icon, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(menu_route_icon, LV_OBJ_FLAG_HIDDEN); @@ -699,7 +701,9 @@ void createTopBar() lv_obj_add_flag(menu_wifi_icon, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(menu_team_icon, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(menu_msg_icon, LV_OBJ_FLAG_HIDDEN); +#if defined(TRAIL_MATE_ENABLE_BLE) && TRAIL_MATE_ENABLE_BLE lv_obj_add_flag(menu_ble_icon, LV_OBJ_FLAG_HIDDEN); +#endif } else { diff --git a/modules/ui_shared/src/ui/presentation_sources/runtime_mesh_status_source.cpp b/modules/ui_shared/src/ui/presentation_sources/runtime_mesh_status_source.cpp index b5b23488..9cb3c7a7 100644 --- a/modules/ui_shared/src/ui/presentation_sources/runtime_mesh_status_source.cpp +++ b/modules/ui_shared/src/ui/presentation_sources/runtime_mesh_status_source.cpp @@ -9,7 +9,11 @@ #include -#if __has_include("ble/ble_manager.h") +#ifndef TRAIL_MATE_ENABLE_BLE +#define TRAIL_MATE_ENABLE_BLE 0 +#endif + +#if TRAIL_MATE_ENABLE_BLE && __has_include("ble/ble_manager.h") #include "ble/ble_manager.h" #define TRAIL_MATE_UI_PRESENTATION_HAS_BLE_MANAGER_HEADER 1 #else @@ -39,6 +43,7 @@ bool RuntimeMeshStatusSource::buildMeshStatusSnapshot( .getTotalUnread()); const uint32_t unread = chat_unread + team_unread; +#if TRAIL_MATE_ENABLE_BLE bool ble_active = app::runtimeFacade().isBleEnabled(); bool ble_linked = false; #if TRAIL_MATE_UI_PRESENTATION_HAS_BLE_MANAGER_HEADER @@ -51,6 +56,7 @@ bool RuntimeMeshStatusSource::buildMeshStatusSnapshot( ble_linked = ble_status.is_connected; } } +#endif #endif out.team_linked = has_team; @@ -62,10 +68,14 @@ bool RuntimeMeshStatusSource::buildMeshStatusSnapshot( ui::copyText(out.protocol_label, chat::infra::meshProtocolName( app::configFacade().getConfig().mesh_protocol)); +#if TRAIL_MATE_ENABLE_BLE ui::copyText(out.radio_label, ble_linked ? "BLE linked" : (ble_active ? "BLE bridge ready" : "LoRa direct path")); +#else + ui::copyText(out.radio_label, "LoRa direct path"); +#endif char status[64]{}; std::snprintf(status, diff --git a/modules/ui_shared/src/ui/screens/settings/settings_channel_actions.cpp b/modules/ui_shared/src/ui/screens/settings/settings_channel_actions.cpp new file mode 100644 index 00000000..dfa8b075 --- /dev/null +++ b/modules/ui_shared/src/ui/screens/settings/settings_channel_actions.cpp @@ -0,0 +1,304 @@ +#include "ui/screens/settings/settings_channel_actions.h" + +#include "chat/domain/chat_types.h" +#include +#include + +#if defined(ESP_PLATFORM) +#include "esp_random.h" +#endif + +namespace settings::ui::channel +{ + +namespace +{ + +void copy_bounded(char* out, std::size_t out_len, const char* text) +{ + if (!out || out_len == 0) + { + return; + } + if (!text) + { + out[0] = '\0'; + return; + } + std::strncpy(out, text, out_len - 1); + out[out_len - 1] = '\0'; +} + +bool parse_hex_char(char c, std::uint8_t& out) +{ + if (c >= '0' && c <= '9') + { + out = static_cast(c - '0'); + return true; + } + if (c >= 'a' && c <= 'f') + { + out = static_cast(10 + (c - 'a')); + return true; + } + if (c >= 'A' && c <= 'F') + { + out = static_cast(10 + (c - 'A')); + return true; + } + return false; +} + +void fill_random_bytes(std::uint8_t* out, std::size_t len) +{ + if (!out) + { + return; + } + std::size_t offset = 0; + while (offset < len) + { +#if defined(ESP_PLATFORM) + const std::uint32_t value = esp_random(); +#else + const std::uint32_t value = static_cast(std::rand()); +#endif + for (std::size_t byte_index = 0; byte_index < sizeof(value) && offset < len; + ++byte_index) + { + out[offset++] = + static_cast((value >> (byte_index * 8U)) & 0xFFU); + } + } +} + +bool generate_hex_key(char* out, std::size_t out_len, std::size_t key_len) +{ + if (!out || out_len == 0) + { + return false; + } + out[0] = '\0'; + if (key_len == 0 || key_len > chat::kMeshtasticChannelKeyMaxLen) + { + return false; + } + std::uint8_t key[chat::kMeshtasticChannelKeyMaxLen] = {}; + fill_random_bytes(key, key_len); + bytes_to_hex(key, key_len, out, out_len); + return out[0] != '\0'; +} + +} // namespace + +bool is_zero_key(const std::uint8_t* key, std::size_t len) +{ + if (!key || len == 0) + { + return true; + } + for (std::size_t i = 0; i < len; ++i) + { + if (key[i] != 0) + { + return false; + } + } + return true; +} + +void bytes_to_hex(const std::uint8_t* data, std::size_t len, char* out, std::size_t out_len) +{ + if (!out || out_len == 0) + { + return; + } + out[0] = '\0'; + if (!data || len == 0) + { + return; + } + constexpr char kHex[] = "0123456789ABCDEF"; + const std::size_t required = len * 2 + 1; + if (out_len < required) + { + return; + } + for (std::size_t i = 0; i < len; ++i) + { + const std::uint8_t b = data[i]; + out[i * 2] = kHex[b >> 4]; + out[i * 2 + 1] = kHex[b & 0x0F]; + } + out[len * 2] = '\0'; +} + +bool parse_psk(const char* text, + std::uint8_t* out, + std::size_t out_len, + std::size_t* parsed_len) +{ + if (!out || out_len == 0) + { + return false; + } + if (parsed_len) + { + *parsed_len = 0; + } + if (!text || text[0] == '\0') + { + std::memset(out, 0, out_len); + return true; + } + const std::size_t len = std::strlen(text); + if ((len == 32 || len == 64) && out_len >= len / 2) + { + const std::size_t byte_len = len / 2; + for (std::size_t i = 0; i < byte_len; ++i) + { + std::uint8_t hi = 0; + std::uint8_t lo = 0; + if (!parse_hex_char(text[i * 2], hi) || !parse_hex_char(text[i * 2 + 1], lo)) + { + return false; + } + out[i] = static_cast((hi << 4) | lo); + } + if (parsed_len) + { + *parsed_len = byte_len; + } + return true; + } + if ((len == 16 || len == 32) && out_len >= len) + { + std::memcpy(out, text, len); + if (parsed_len) + { + *parsed_len = len; + } + return true; + } + return false; +} + +void meshtastic_key_to_text(const std::uint8_t* key, + std::size_t key_capacity, + std::uint8_t stored_len, + char* out, + std::size_t out_len) +{ + const std::uint8_t key_len = + chat::normalizeMeshtasticChannelKeyLen(key, key_capacity, stored_len); + if (key_len == 0) + { + if (out && out_len > 0) + { + out[0] = '\0'; + } + return; + } + bytes_to_hex(key, key_len, out, out_len); +} + +bool parse_meshtastic_key_text(const char* text, + std::uint8_t* key, + std::size_t key_capacity, + std::uint8_t* out_key_len) +{ + if (!key || key_capacity < chat::kMeshtasticChannelKeyDefaultLen || !out_key_len) + { + return false; + } + std::uint8_t parsed[chat::kMeshtasticChannelKeyMaxLen] = {}; + std::size_t parsed_len = 0; + if (!parse_psk(text, parsed, key_capacity, &parsed_len)) + { + return false; + } + std::memset(key, 0, key_capacity); + if (parsed_len > 0) + { + std::memcpy(key, parsed, parsed_len); + } + *out_key_len = + chat::normalizeMeshtasticChannelKeyLen(key, + key_capacity, + static_cast(parsed_len)); + return true; +} + +void sync_meshtastic_channel_fields(const app::AppConfig& config, SettingsData& settings) +{ + settings.mt_primary_enabled = config.primary_enabled; + copy_bounded(settings.mt_primary_name, + sizeof(settings.mt_primary_name), + config.meshtastic_config.primary_channel_name); + meshtastic_key_to_text(config.meshtastic_config.primary_key, + sizeof(config.meshtastic_config.primary_key), + config.meshtastic_config.primary_key_len, + settings.mt_primary_key, + sizeof(settings.mt_primary_key)); + settings.mt_primary_uplink = config.primary_uplink_enabled; + settings.mt_primary_downlink = config.primary_downlink_enabled; + + settings.mt_secondary_enabled = config.secondary_enabled; + copy_bounded(settings.mt_secondary_name, + sizeof(settings.mt_secondary_name), + config.meshtastic_config.secondary_channel_name); + meshtastic_key_to_text(config.meshtastic_config.secondary_key, + sizeof(config.meshtastic_config.secondary_key), + config.meshtastic_config.secondary_key_len, + settings.mt_secondary_key, + sizeof(settings.mt_secondary_key)); + settings.mt_secondary_uplink = config.secondary_uplink_enabled; + settings.mt_secondary_downlink = config.secondary_downlink_enabled; +} + +bool generate_meshtastic_channel_key(app::AppConfig& config, + SettingsData& settings, + bool primary) +{ + char generated[65] = {}; + if (!generate_hex_key(generated, + sizeof(generated), + chat::kMeshtasticChannelKeyDefaultLen)) + { + return false; + } + + chat::MeshConfig& mesh = config.meshtastic_config; + std::uint8_t* key = primary ? mesh.primary_key : mesh.secondary_key; + std::uint8_t* key_len = primary ? &mesh.primary_key_len : &mesh.secondary_key_len; + if (!parse_meshtastic_key_text(generated, + key, + chat::kMeshtasticChannelKeyMaxLen, + key_len)) + { + return false; + } + copy_bounded(primary ? settings.mt_primary_key : settings.mt_secondary_key, + primary ? sizeof(settings.mt_primary_key) : sizeof(settings.mt_secondary_key), + generated); + return true; +} + +bool generate_meshcore_channel_key(app::AppConfig& config, SettingsData& settings) +{ + char generated[33] = {}; + if (!generate_hex_key(generated, sizeof(generated), chat::kMeshCoreChannelKeyLen)) + { + return false; + } + std::uint8_t key[chat::kMeshCoreChannelKeyLen] = {}; + if (!parse_psk(generated, key, sizeof(key))) + { + return false; + } + std::memcpy(config.meshcore_config.secondary_key, key, sizeof(key)); + copy_bounded(settings.mc_channel_key, sizeof(settings.mc_channel_key), generated); + return true; +} + +} // namespace settings::ui::channel diff --git a/modules/ui_shared/src/ui/screens/settings/settings_page_components.cpp b/modules/ui_shared/src/ui/screens/settings/settings_page_components.cpp index 0b13ef4b..db7171a5 100644 --- a/modules/ui_shared/src/ui/screens/settings/settings_page_components.cpp +++ b/modules/ui_shared/src/ui/screens/settings/settings_page_components.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include "app/app_config.h" #include "app/app_facade_access.h" @@ -42,10 +41,12 @@ #include "ui/page/page_profile.h" #include "ui/presentation_sources/runtime_settings_source.h" #include "ui/runtime/ui_feedback.h" +#include "ui/screens/settings/settings_channel_actions.h" #include "ui/screens/settings/settings_page_components.h" #include "ui/screens/settings/settings_page_input.h" #include "ui/screens/settings/settings_page_layout.h" #include "ui/screens/settings/settings_page_styles.h" +#include "ui/screens/settings/settings_spec.h" #include "ui/screens/settings/settings_state.h" #include "ui/ui_common.h" #include "ui/widgets/foreground_operation_overlay.h" @@ -83,7 +84,7 @@ namespace tracker_runtime = ::platform::ui::tracker; namespace wireless_companion_runtime = ::platform::ui::wireless_companion; namespace wifi_runtime = ::platform::ui::wifi; -constexpr size_t kMaxItems = 32; +constexpr size_t kMaxItems = 48; constexpr size_t kMaxOptions = 40; constexpr size_t kMaxWifiNetworks = 8; constexpr size_t kChatRegionOptionCapacity = 32; @@ -338,6 +339,7 @@ static void append_custom_timezone_option_if_needed(int profile_id, int offset_m } static void update_item_value(settings::ui::ItemWidget& widget); +static settings::ui::SettingId item_id(const settings::ui::SettingItem& item); static void open_factory_reset_modal(); static void open_settings_restore_modal(); static void open_gps_diagnostics_modal(); @@ -444,21 +446,19 @@ static void clear_wifi_scan_options() g_settings.wifi_network_index = -1; } -static void rebuild_wifi_scan_options(const std::vector& results) +static void rebuild_wifi_scan_options(size_t result_count) { - clear_wifi_scan_options(); auto& options = dynamic_options(); - const size_t limit = results.size() < kMaxWifiNetworks ? results.size() : kMaxWifiNetworks; + const size_t limit = result_count < kMaxWifiNetworks ? result_count : kMaxWifiNetworks; for (size_t i = 0; i < limit; ++i) { - options.wifi_scan_results[i] = results[i]; std::snprintf(options.wifi_network_option_labels[i], sizeof(options.wifi_network_option_labels[i]), "%s (%d dBm%s)", - results[i].ssid, - results[i].rssi, - results[i].requires_password ? ", lock" : ""); + options.wifi_scan_results[i].ssid, + options.wifi_scan_results[i].rssi, + options.wifi_scan_results[i].requires_password ? ", lock" : ""); options.wifi_network_options[i].label = options.wifi_network_option_labels[i]; options.wifi_network_options[i].value = static_cast(i); } @@ -922,10 +922,10 @@ static void create_item_content(settings::ui::ItemWidget& widget, lv_obj_t* btn) return; } + const settings::ui::SettingId id = item_id(*widget.def); const bool reticulum_hash_item = - widget.def->pref_key && - (strcmp(widget.def->pref_key, "rt_identity_hash") == 0 || - strcmp(widget.def->pref_key, "rt_lxmf_address") == 0); + id == settings::ui::SettingId::RtIdentityHash || + id == settings::ui::SettingId::RtLxmfAddress; if (reticulum_hash_item) { lv_obj_set_height(btn, ::ui::page_profile::is_dense() ? 64 : 72); @@ -1042,28 +1042,6 @@ static bool prefs_get_bool(const char* key, bool default_value) return prefs_get_bool_ns(kPrefsNs, key, default_value); } -static bool is_settings_store_owned_enum_setting(const char* key) -{ - if (!key) - { - return false; - } - return strcmp(key, "screen_brightness") == 0 || - strcmp(key, "speaker_volume") == 0 || - strcmp(key, "chat_message_alerts") == 0 || - strcmp(key, "chat_contact_alerts") == 0; -} - -static bool is_settings_store_owned_toggle_setting(const char* key) -{ - if (!key) - { - return false; - } - return strcmp(key, "vibration_enabled") == 0 || - strcmp(key, "adv_debug") == 0; -} - static uint8_t get_message_tone_volume_default() { return device_runtime::default_message_tone_volume(); @@ -1119,48 +1097,6 @@ static void apply_track_format_runtime(uint8_t format) tracker_runtime::set_format(static_cast(format)); } -static bool is_zero_key(const uint8_t* key, size_t len) -{ - if (!key || len == 0) - { - return true; - } - for (size_t i = 0; i < len; ++i) - { - if (key[i] != 0) - { - return false; - } - } - return true; -} - -static void bytes_to_hex(const uint8_t* data, size_t len, char* out, size_t out_len) -{ - if (!out || out_len == 0) - { - return; - } - out[0] = '\0'; - if (!data || len == 0) - { - return; - } - static const char* kHex = "0123456789ABCDEF"; - size_t required = len * 2 + 1; - if (out_len < required) - { - return; - } - for (size_t i = 0; i < len; ++i) - { - uint8_t b = data[i]; - out[i * 2] = kHex[b >> 4]; - out[i * 2 + 1] = kHex[b & 0x0F]; - } - out[len * 2] = '\0'; -} - static void bytes_to_wrapped_hash(const uint8_t* data, char* out, size_t out_len) { if (!out || out_len == 0) @@ -1234,73 +1170,6 @@ static void refresh_reticulum_identity_fields_from_runtime() refresh_reticulum_identity_fields(app_ctx, app_ctx.getConfig()); } -static bool parse_hex_char(char c, uint8_t& out) -{ - if (c >= '0' && c <= '9') - { - out = static_cast(c - '0'); - return true; - } - if (c >= 'a' && c <= 'f') - { - out = static_cast(10 + (c - 'a')); - return true; - } - if (c >= 'A' && c <= 'F') - { - out = static_cast(10 + (c - 'A')); - return true; - } - return false; -} - -static bool parse_psk(const char* text, uint8_t* out, size_t out_len, size_t* parsed_len = nullptr) -{ - if (!out || out_len == 0) - { - return false; - } - if (parsed_len) - { - *parsed_len = 0; - } - if (!text || text[0] == '\0') - { - memset(out, 0, out_len); - return true; - } - size_t len = strlen(text); - if ((len == 32 || len == 64) && out_len >= len / 2) - { - const size_t byte_len = len / 2; - for (size_t i = 0; i < byte_len; ++i) - { - uint8_t hi = 0; - uint8_t lo = 0; - if (!parse_hex_char(text[i * 2], hi) || !parse_hex_char(text[i * 2 + 1], lo)) - { - return false; - } - out[i] = static_cast((hi << 4) | lo); - } - if (parsed_len) - { - *parsed_len = byte_len; - } - return true; - } - if ((len == 16 || len == 32) && out_len >= len) - { - memcpy(out, text, len); - if (parsed_len) - { - *parsed_len = len; - } - return true; - } - return false; -} - static void float_to_text(float value, char* out, size_t out_len, uint8_t decimals = 2) { if (!out || out_len == 0) @@ -1398,6 +1267,12 @@ static bool reticulum_wifi_settings_visible() chat::ReticulumInterfacePolicy::LoRaOnly; } +static bool reticulum_lora_settings_visible() +{ + return reticulum_bearer_policy_from_value(g_settings.rt_bearer_policy) != + chat::ReticulumInterfacePolicy::WifiGatewayOnly; +} + static void reset_mesh_settings() { app::IAppFacade& app_ctx = app::appFacade(); @@ -1418,6 +1293,7 @@ static void reset_mesh_settings() g_settings.chat_region = app_ctx.getConfig().meshtastic_config.region; g_settings.chat_channel = 0; g_settings.chat_psk[0] = '\0'; + ::settings::ui::channel::sync_meshtastic_channel_fields(app_ctx.getConfig(), g_settings); g_settings.net_use_preset = app_ctx.getConfig().meshtastic_config.use_preset; g_settings.net_modem_preset = app_ctx.getConfig().meshtastic_config.modem_preset; g_settings.net_tx_power = app_ctx.getConfig().activeMeshConfig().tx_power; @@ -1461,6 +1337,16 @@ static void reset_mesh_settings() "mesh_protocol", "chat_channel", "chat_psk", + "mt_primary_enabled", + "mt_primary_name", + "mt_primary_key", + "mt_primary_uplink", + "mt_primary_downlink", + "mt_secondary_enabled", + "mt_secondary_name", + "mt_secondary_key", + "mt_secondary_uplink", + "mt_secondary_downlink", "mt_mqtt_enabled", "mt_mqtt_uplink", "mt_mqtt_downlink", @@ -1682,6 +1568,7 @@ static void settings_load() { g_settings.chat_contact_alerts = kChatContactAlertsContacts; } + ::settings::ui::channel::sync_meshtastic_channel_fields(cfg, g_settings); const uint8_t* active_psk = nullptr; size_t active_psk_len = 0; if (cfg.mesh_protocol == chat::MeshProtocol::MeshCore) @@ -1696,16 +1583,17 @@ static void settings_load() sizeof(mt_cfg.secondary_key), mt_cfg.secondary_key_len); } - if (!active_psk || active_psk_len == 0 || is_zero_key(active_psk, active_psk_len)) + if (!active_psk || active_psk_len == 0 || + ::settings::ui::channel::is_zero_key(active_psk, active_psk_len)) { g_settings.chat_psk[0] = '\0'; } else { - bytes_to_hex(active_psk, - active_psk_len, - g_settings.chat_psk, - sizeof(g_settings.chat_psk)); + ::settings::ui::channel::bytes_to_hex(active_psk, + active_psk_len, + g_settings.chat_psk, + sizeof(g_settings.chat_psk)); } refresh_reticulum_identity_fields(app_ctx, cfg); @@ -1809,14 +1697,17 @@ static void settings_load() g_settings.mc_channel_slot = mc_cfg.meshcore_channel_slot; strncpy(g_settings.mc_channel_name, mc_cfg.meshcore_channel_name, sizeof(g_settings.mc_channel_name) - 1); g_settings.mc_channel_name[sizeof(g_settings.mc_channel_name) - 1] = '\0'; - if (is_zero_key(mc_cfg.secondary_key, chat::kMeshCoreChannelKeyLen)) + if (::settings::ui::channel::is_zero_key(mc_cfg.secondary_key, + chat::kMeshCoreChannelKeyLen)) { g_settings.mc_channel_key[0] = '\0'; } else { - bytes_to_hex(mc_cfg.secondary_key, chat::kMeshCoreChannelKeyLen, - g_settings.mc_channel_key, sizeof(g_settings.mc_channel_key)); + ::settings::ui::channel::bytes_to_hex(mc_cfg.secondary_key, + chat::kMeshCoreChannelKeyLen, + g_settings.mc_channel_key, + sizeof(g_settings.mc_channel_key)); } g_settings.mc_mqtt_enabled = mc_cfg.meshcore_mqtt_enabled; g_settings.mc_mqtt_uplink = mc_cfg.meshcore_mqtt_uplink_enabled; @@ -1865,7 +1756,6 @@ static void settings_load() apply_message_tone_volume(static_cast(g_settings.speaker_volume)); g_settings.display_locale_index = ::ui::i18n::current_locale_index(); - g_settings.ble_enabled = cfg.ble_enabled; g_settings.vibration_enabled = prefs_get_bool("vibration_enabled", true); refresh_wifi_state_from_runtime(); refresh_firmware_update_state_from_runtime(); @@ -1982,15 +1872,15 @@ static void format_value(const settings::ui::SettingItem& item, char* out, size_ } break; case settings::ui::SettingType::Action: - if (item.pref_key && std::strcmp(item.pref_key, "enabled_imes") == 0) + if (item_id(item) == settings::ui::SettingId::EnabledImes) { format_enabled_ime_summary(out, out_len); } - else if (item.pref_key && std::strcmp(item.pref_key, "manual_time_set") == 0) + else if (item_id(item) == settings::ui::SettingId::ManualTimeSet) { format_manual_datetime_summary(out, out_len); } - else if (item.pref_key && std::strcmp(item.pref_key, "c6_enter_download") == 0) + else if (item_id(item) == settings::ui::SettingId::C6EnterDownload) { snprintf(out, out_len, "%s", ::ui::i18n::tr("Enter")); } @@ -2010,9 +1900,10 @@ static void update_item_value(settings::ui::ItemWidget& widget) } char value[48]; format_value(*widget.def, value, sizeof(value)); + const settings::ui::SettingId id = item_id(*widget.def); const bool use_content_font = option_labels_use_content_font(*widget.def) || - (widget.def->pref_key && std::strcmp(widget.def->pref_key, "enabled_imes") == 0 && + (id == settings::ui::SettingId::EnabledImes && ::ui::fonts::utf8_has_non_ascii(value)); if (use_content_font) { @@ -2298,10 +2189,9 @@ static void on_text_save_clicked(lv_event_t* e) { strncpy(g_state.editing_item->text_value, text, g_state.editing_item->text_max - 1); g_state.editing_item->text_value[g_state.editing_item->text_max - 1] = '\0'; - bool is_user_name = (g_state.editing_item->pref_key && - strcmp(g_state.editing_item->pref_key, "chat_user") == 0); - bool is_short_name = (g_state.editing_item->pref_key && - strcmp(g_state.editing_item->pref_key, "chat_short") == 0); + const settings::ui::SettingId id = item_id(*g_state.editing_item); + bool is_user_name = id == settings::ui::SettingId::ChatUser; + bool is_short_name = id == settings::ui::SettingId::ChatShort; update_item_value(*g_state.editing_widget); bool broadcast_nodeinfo = false; if (is_user_name) @@ -2328,7 +2218,59 @@ static void on_text_save_clicked(lv_event_t* e) { app::appFacade().broadcastNodeInfo(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "chat_psk") == 0) + if (id == settings::ui::SettingId::MtPrimaryName) + { + app::IAppFacade& app_ctx = app::appFacade(); + copy_bounded(app_ctx.getConfig().meshtastic_config.primary_channel_name, + sizeof(app_ctx.getConfig().meshtastic_config.primary_channel_name), + g_state.editing_item->text_value); + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + } + if (id == settings::ui::SettingId::MtSecondaryName) + { + app::IAppFacade& app_ctx = app::appFacade(); + copy_bounded(app_ctx.getConfig().meshtastic_config.secondary_channel_name, + sizeof(app_ctx.getConfig().meshtastic_config.secondary_channel_name), + g_state.editing_item->text_value); + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + } + if (id == settings::ui::SettingId::MtPrimaryKey) + { + app::IAppFacade& app_ctx = app::appFacade(); + chat::MeshConfig& mesh = app_ctx.getConfig().meshtastic_config; + if (!::settings::ui::channel::parse_meshtastic_key_text( + g_state.editing_item->text_value, + mesh.primary_key, + sizeof(mesh.primary_key), + &mesh.primary_key_len)) + { + ::ui::feedback::show_notice(::ui::i18n::tr("PSK must be 32/64 hex or 16/32 chars"), 4000); + modal_close(); + return; + } + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + } + if (id == settings::ui::SettingId::MtSecondaryKey) + { + app::IAppFacade& app_ctx = app::appFacade(); + chat::MeshConfig& mesh = app_ctx.getConfig().meshtastic_config; + if (!::settings::ui::channel::parse_meshtastic_key_text( + g_state.editing_item->text_value, + mesh.secondary_key, + sizeof(mesh.secondary_key), + &mesh.secondary_key_len)) + { + ::ui::feedback::show_notice(::ui::i18n::tr("PSK must be 32/64 hex or 16/32 chars"), 4000); + modal_close(); + return; + } + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + } + if (id == settings::ui::SettingId::ChatPsk) { app::IAppFacade& app_ctx = app::appFacade(); uint8_t key[chat::kMeshtasticChannelKeyMaxLen] = {}; @@ -2337,7 +2279,10 @@ static void on_text_save_clicked(lv_event_t* e) (app_ctx.getConfig().mesh_protocol == chat::MeshProtocol::MeshCore) ? chat::kMeshCoreChannelKeyLen : chat::kMeshtasticChannelKeyMaxLen; - if (!parse_psk(g_state.editing_item->text_value, key, key_capacity, &parsed_key_len)) + if (!::settings::ui::channel::parse_psk(g_state.editing_item->text_value, + key, + key_capacity, + &parsed_key_len)) { ::ui::feedback::show_notice(::ui::i18n::tr("PSK must be 32/64 hex or 16/32 chars"), 4000); modal_close(); @@ -2362,7 +2307,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "net_freq_offset") == 0) + if (id == settings::ui::SettingId::NetFreqOffset) { app::IAppFacade& app_ctx = app::appFacade(); float value = 0.0f; @@ -2376,7 +2321,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "net_override_freq") == 0) + if (id == settings::ui::SettingId::NetOverrideFreq) { app::IAppFacade& app_ctx = app::appFacade(); float value = 0.0f; @@ -2397,7 +2342,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_freq") == 0) + if (id == settings::ui::SettingId::McFreq) { app::IAppFacade& app_ctx = app::appFacade(); float value = 0.0f; @@ -2413,7 +2358,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_bw") == 0) + if (id == settings::ui::SettingId::McBw) { app::IAppFacade& app_ctx = app::appFacade(); float value = 0.0f; @@ -2429,7 +2374,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_rx_delay") == 0) + if (id == settings::ui::SettingId::McRxDelay) { app::IAppFacade& app_ctx = app::appFacade(); float value = 0.0f; @@ -2443,7 +2388,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_airtime") == 0) + if (id == settings::ui::SettingId::McAirtime) { app::IAppFacade& app_ctx = app::appFacade(); float value = 0.0f; @@ -2457,7 +2402,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_channel_name") == 0) + if (id == settings::ui::SettingId::McChannelName) { app::IAppFacade& app_ctx = app::appFacade(); strncpy(app_ctx.getConfig().meshcore_config.meshcore_channel_name, @@ -2467,11 +2412,13 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_channel_key") == 0) + if (id == settings::ui::SettingId::McChannelKey) { app::IAppFacade& app_ctx = app::appFacade(); uint8_t key[16] = {}; - if (!parse_psk(g_state.editing_item->text_value, key, sizeof(key))) + if (!::settings::ui::channel::parse_psk(g_state.editing_item->text_value, + key, + sizeof(key))) { ::ui::feedback::show_notice(::ui::i18n::tr("Key must be 32 hex or 16 chars"), 3000); modal_close(); @@ -2481,7 +2428,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "gauge_design_mah") == 0) + if (id == settings::ui::SettingId::GaugeDesignMah) { // Update gauge design capacity (mAh) in the shared "power" settings namespace. char* end = nullptr; @@ -2495,7 +2442,7 @@ static void on_text_save_clicked(lv_event_t* e) prefs_put_uint_ns("power", "gauge_design_mah", static_cast(value)); device_runtime::reload_configurable_battery_gauge(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "gauge_full_mah") == 0) + if (id == settings::ui::SettingId::GaugeFullMah) { // Update gauge full-charge capacity (mAh) that shares the same profile with design capacity. char* end = nullptr; @@ -2509,7 +2456,7 @@ static void on_text_save_clicked(lv_event_t* e) prefs_put_uint_ns("power", "gauge_full_mah", static_cast(value)); device_runtime::reload_configurable_battery_gauge(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "rt_wifi_host") == 0) + if (id == settings::ui::SettingId::RtWifiHost) { app::IAppFacade& app_ctx = app::appFacade(); copy_bounded(app_ctx.getConfig().reticulumConfig().reticulum_wifi_gateway_host, @@ -2518,7 +2465,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "rt_wifi_port") == 0) + if (id == settings::ui::SettingId::RtWifiPort) { char* end = nullptr; long value = strtol(g_state.editing_item->text_value, &end, 10); @@ -2539,7 +2486,7 @@ static void on_text_save_clicked(lv_event_t* e) "%u", static_cast(value)); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mt_mqtt_host") == 0) + if (id == settings::ui::SettingId::MtMqttHost) { app::IAppFacade& app_ctx = app::appFacade(); copy_bounded(app_ctx.getConfig().meshtastic_mqtt_host, @@ -2547,7 +2494,7 @@ static void on_text_save_clicked(lv_event_t* e) g_state.editing_item->text_value); app_ctx.saveConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mt_mqtt_port") == 0) + if (id == settings::ui::SettingId::MtMqttPort) { char* end = nullptr; long value = strtol(g_state.editing_item->text_value, &end, 10); @@ -2567,7 +2514,7 @@ static void on_text_save_clicked(lv_event_t* e) "%u", static_cast(value)); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mt_mqtt_root") == 0) + if (id == settings::ui::SettingId::MtMqttRoot) { app::IAppFacade& app_ctx = app::appFacade(); const char* root = @@ -2581,7 +2528,7 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); std::printf("[Settings][MQTT] mt root saved root=%s\n", root); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mt_mqtt_user") == 0) + if (id == settings::ui::SettingId::MtMqttUser) { app::IAppFacade& app_ctx = app::appFacade(); copy_bounded(app_ctx.getConfig().meshtastic_mqtt_username, @@ -2589,7 +2536,7 @@ static void on_text_save_clicked(lv_event_t* e) g_state.editing_item->text_value); app_ctx.saveConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mt_mqtt_pass") == 0) + if (id == settings::ui::SettingId::MtMqttPass) { app::IAppFacade& app_ctx = app::appFacade(); copy_bounded(app_ctx.getConfig().meshtastic_mqtt_password, @@ -2597,7 +2544,7 @@ static void on_text_save_clicked(lv_event_t* e) g_state.editing_item->text_value); app_ctx.saveConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_mqtt_host") == 0) + if (id == settings::ui::SettingId::McMqttHost) { app::IAppFacade& app_ctx = app::appFacade(); copy_bounded(app_ctx.getConfig().meshcore_config.meshcore_mqtt_host, @@ -2605,7 +2552,7 @@ static void on_text_save_clicked(lv_event_t* e) g_state.editing_item->text_value); app_ctx.saveConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_mqtt_port") == 0) + if (id == settings::ui::SettingId::McMqttPort) { char* end = nullptr; long value = strtol(g_state.editing_item->text_value, &end, 10); @@ -2625,7 +2572,7 @@ static void on_text_save_clicked(lv_event_t* e) "%u", static_cast(value)); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_mqtt_root") == 0) + if (id == settings::ui::SettingId::McMqttRoot) { app::IAppFacade& app_ctx = app::appFacade(); const char* root = @@ -2638,7 +2585,7 @@ static void on_text_save_clicked(lv_event_t* e) copy_bounded(g_settings.mc_mqtt_root, sizeof(g_settings.mc_mqtt_root), root); app_ctx.saveConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_mqtt_user") == 0) + if (id == settings::ui::SettingId::McMqttUser) { app::IAppFacade& app_ctx = app::appFacade(); copy_bounded(app_ctx.getConfig().meshcore_config.meshcore_mqtt_username, @@ -2646,7 +2593,7 @@ static void on_text_save_clicked(lv_event_t* e) g_state.editing_item->text_value); app_ctx.saveConfig(); } - if (g_state.editing_item->pref_key && strcmp(g_state.editing_item->pref_key, "mc_mqtt_pass") == 0) + if (id == settings::ui::SettingId::McMqttPass) { app::IAppFacade& app_ctx = app::appFacade(); copy_bounded(app_ctx.getConfig().meshcore_config.meshcore_mqtt_password, @@ -2655,8 +2602,8 @@ static void on_text_save_clicked(lv_event_t* e) app_ctx.saveConfig(); } if (g_state.editing_item->pref_key && - (strcmp(g_state.editing_item->pref_key, "wifi_ssid") == 0 || - strcmp(g_state.editing_item->pref_key, "wifi_password") == 0)) + (id == settings::ui::SettingId::WifiSsid || + id == settings::ui::SettingId::WifiPassword)) { wifi_runtime::Config config{}; config.enabled = g_settings.wifi_enabled; @@ -3216,13 +3163,14 @@ static void on_option_clicked(lv_event_t* e) bool rebuild_active_app = false; bool refresh_menu_labels = false; int previous_value = *payload->item->enum_value; + const settings::ui::SettingId id = item_id(*payload->item); *payload->item->enum_value = payload->value; - if (is_settings_store_owned_enum_setting(payload->item->pref_key)) + if (::settings::ui::spec::is_settings_store_owned_enum(id)) { prefs_put_int(payload->item->pref_key, payload->value); } update_item_value(*payload->widget); - if (payload->item->pref_key && strcmp(payload->item->pref_key, "display_locale") == 0) + if (id == settings::ui::SettingId::DisplayLocale) { if (::ui::i18n::set_locale_by_index(static_cast(payload->value), true)) { @@ -3236,7 +3184,7 @@ static void on_option_clicked(lv_event_t* e) g_settings.display_locale_index = ::ui::i18n::current_locale_index(); update_item_value(*payload->widget); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "wifi_network") == 0) + if (id == settings::ui::SettingId::WifiNetwork) { if (payload->value >= 0 && static_cast(payload->value) < kWifiNetworkOptionCount) @@ -3262,7 +3210,7 @@ static void on_option_clicked(lv_event_t* e) rebuild_list = true; } } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "rt_bearer") == 0) + if (id == settings::ui::SettingId::RtBearer) { app::IAppFacade& app_ctx = app::appFacade(); chat::MeshConfig& reticulum = app_ctx.getConfig().reticulumConfig(); @@ -3276,7 +3224,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.applyMeshConfig(); rebuild_list = true; } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mesh_protocol") == 0) + if (id == settings::ui::SettingId::MeshProtocol) { app::IAppFacade& app_ctx = app::appFacade(); chat::MeshProtocol target = chat::infra::normalizeMeshProtocol( @@ -3294,7 +3242,7 @@ static void on_option_clicked(lv_event_t* e) restart_now = true; } } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "chat_region") == 0) + if (id == settings::ui::SettingId::ChatRegion) { app::IAppFacade& app_ctx = app::appFacade(); chat::MeshConfig& mt_cfg = app_ctx.getConfig().meshtastic_config; @@ -3321,7 +3269,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.applyMeshConfig(); app_ctx.applyNetworkLimits(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_use_preset") == 0) + if (id == settings::ui::SettingId::NetUsePreset) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_config.use_preset = (payload->value != 0); @@ -3329,7 +3277,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.applyMeshConfig(); rebuild_list = true; } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_preset") == 0) + if (id == settings::ui::SettingId::NetPreset) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_config.modem_preset = static_cast(payload->value); @@ -3339,7 +3287,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.applyMeshConfig(); rebuild_list = true; } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_bw") == 0) + if (id == settings::ui::SettingId::NetBw) { app::IAppFacade& app_ctx = app::appFacade(); if (chat::infra::isReticulumMeshProtocol(app_ctx.getConfig().mesh_protocol)) @@ -3356,7 +3304,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.applyMeshConfig(); rebuild_list = true; } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_sf") == 0) + if (id == settings::ui::SettingId::NetSf) { app::IAppFacade& app_ctx = app::appFacade(); if (chat::infra::isReticulumMeshProtocol(app_ctx.getConfig().mesh_protocol)) @@ -3373,7 +3321,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.applyMeshConfig(); rebuild_list = true; } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_cr") == 0) + if (id == settings::ui::SettingId::NetCr) { app::IAppFacade& app_ctx = app::appFacade(); if (chat::infra::isReticulumMeshProtocol(app_ctx.getConfig().mesh_protocol)) @@ -3390,31 +3338,31 @@ static void on_option_clicked(lv_event_t* e) app_ctx.applyMeshConfig(); rebuild_list = true; } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_hop_limit") == 0) + if (id == settings::ui::SettingId::NetHopLimit) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_config.hop_limit = static_cast(payload->value); app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_channel_num") == 0) + if (id == settings::ui::SettingId::NetChannelNum) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_config.channel_num = static_cast(payload->value); app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "screen_timeout") == 0) + if (id == settings::ui::SettingId::ScreenTimeout) { screen_runtime::set_timeout_ms(static_cast(payload->value)); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "screen_brightness") == 0) + if (id == settings::ui::SettingId::ScreenBrightness) { const uint8_t brightness = static_cast(clamp_screen_brightness(payload->value)); g_settings.screen_brightness = brightness; device_runtime::set_screen_brightness(brightness); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "speaker_volume") == 0) + if (id == settings::ui::SettingId::SpeakerVolume) { const uint8_t volume = static_cast(payload->value); apply_message_tone_volume(volume); @@ -3424,7 +3372,7 @@ static void on_option_clicked(lv_event_t* e) play_message_tone_preview(); } } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_interval") == 0) + if (id == settings::ui::SettingId::GpsInterval) { app::IAppFacade& app_ctx = app::appFacade(); uint32_t interval_ms = static_cast(payload->value) * 1000u; @@ -3432,14 +3380,14 @@ static void on_option_clicked(lv_event_t* e) app_ctx.saveConfig(); gps_runtime::set_collection_interval(interval_ms); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_init_baud") == 0) + if (id == settings::ui::SettingId::GpsInitBaud) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_init_baud = static_cast(payload->value); app_ctx.saveConfig(); gps_runtime::set_receiver_init_config(make_gps_receiver_init_config(app_ctx.getConfig())); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_init_probe_ms") == 0) + if (id == settings::ui::SettingId::GpsInitProbeMs) { app::IAppFacade& app_ctx = app::appFacade(); int probe_ms = payload->value; @@ -3455,74 +3403,74 @@ static void on_option_clicked(lv_event_t* e) app_ctx.saveConfig(); gps_runtime::set_receiver_init_config(make_gps_receiver_init_config(app_ctx.getConfig())); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_init_profile") == 0) + if (id == settings::ui::SettingId::GpsInitProfile) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_init_profile = static_cast(payload->value); app_ctx.saveConfig(); gps_runtime::set_receiver_init_config(make_gps_receiver_init_config(app_ctx.getConfig())); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_init_rxm") == 0) + if (id == settings::ui::SettingId::GpsInitRxm) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_init_rxm_policy = static_cast(payload->value); app_ctx.saveConfig(); gps_runtime::set_receiver_init_config(make_gps_receiver_init_config(app_ctx.getConfig())); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_init_gnss") == 0) + if (id == settings::ui::SettingId::GpsInitGnss) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_init_gnss_policy = static_cast(payload->value); app_ctx.saveConfig(); gps_runtime::set_receiver_init_config(make_gps_receiver_init_config(app_ctx.getConfig())); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_init_nmea") == 0) + if (id == settings::ui::SettingId::GpsInitNmea) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_init_nmea_policy = static_cast(payload->value); app_ctx.saveConfig(); gps_runtime::set_receiver_init_config(make_gps_receiver_init_config(app_ctx.getConfig())); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_mode") == 0) + if (id == settings::ui::SettingId::GpsMode) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_mode = static_cast(payload->value); app_ctx.saveConfig(); gps_runtime::set_gnss_config(app_ctx.getConfig().gps_mode, app_ctx.getConfig().gps_sat_mask); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_sat_mask") == 0) + if (id == settings::ui::SettingId::GpsSatMask) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_sat_mask = static_cast(payload->value); app_ctx.saveConfig(); gps_runtime::set_gnss_config(app_ctx.getConfig().gps_mode, app_ctx.getConfig().gps_sat_mask); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_strategy") == 0) + if (id == settings::ui::SettingId::GpsStrategy) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_strategy = static_cast(payload->value); app_ctx.saveConfig(); gps_runtime::set_power_strategy(static_cast(payload->value)); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_alt_ref") == 0) + if (id == settings::ui::SettingId::GpsAltRef) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_alt_ref = static_cast(payload->value); app_ctx.saveConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "gps_coord_fmt") == 0) + if (id == settings::ui::SettingId::GpsCoordFmt) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().gps_coord_format = static_cast(payload->value); app_ctx.saveConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "map_coord") == 0) + if (id == settings::ui::SettingId::MapCoord) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().map_coord_system = static_cast(payload->value); app_ctx.saveConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "map_source") == 0) + if (id == settings::ui::SettingId::MapSource) { app::IAppFacade& app_ctx = app::appFacade(); int source = payload->value; @@ -3533,35 +3481,35 @@ static void on_option_clicked(lv_event_t* e) app_ctx.getConfig().map_source = static_cast(source); app_ctx.saveConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "map_track_interval") == 0) + if (id == settings::ui::SettingId::MapTrackInterval) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().map_track_interval = static_cast(payload->value); app_ctx.saveConfig(); apply_track_interval_runtime(static_cast(payload->value)); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "map_track_format") == 0) + if (id == settings::ui::SettingId::MapTrackFormat) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().map_track_format = static_cast(payload->value); app_ctx.saveConfig(); apply_track_format_runtime(static_cast(payload->value)); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "chat_channel") == 0) + if (id == settings::ui::SettingId::ChatChannel) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().chat_channel = static_cast(payload->value); app_ctx.saveConfig(); app_ctx.applyChatDefaults(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_util") == 0) + if (id == settings::ui::SettingId::NetUtil) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().net_channel_util = static_cast(payload->value); app_ctx.saveConfig(); app_ctx.applyNetworkLimits(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "net_tx_power") == 0) + if (id == settings::ui::SettingId::NetTxPower) { app::IAppFacade& app_ctx = app::appFacade(); if (chat::infra::isReticulumMeshProtocol(app_ctx.getConfig().mesh_protocol)) @@ -3575,7 +3523,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mc_region_preset") == 0) + if (id == settings::ui::SettingId::McRegionPreset) { app::IAppFacade& app_ctx = app::appFacade(); chat::MeshConfig& mc_cfg = app_ctx.getConfig().meshcore_config; @@ -3607,7 +3555,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.applyMeshConfig(); rebuild_list = true; } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mc_sf") == 0) + if (id == settings::ui::SettingId::McSf) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_sf = static_cast(payload->value); @@ -3616,7 +3564,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mc_cr") == 0) + if (id == settings::ui::SettingId::McCr) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_cr = static_cast(payload->value); @@ -3625,7 +3573,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mc_tx_power") == 0) + if (id == settings::ui::SettingId::McTxPower) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.tx_power = static_cast(payload->value); @@ -3634,14 +3582,14 @@ static void on_option_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mc_flood_max") == 0) + if (id == settings::ui::SettingId::McFloodMax) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_flood_max = static_cast(payload->value); app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mc_send_prof") == 0) + if (id == settings::ui::SettingId::McSendProfile) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_send_profile = @@ -3649,7 +3597,7 @@ static void on_option_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mc_fwd_prof") == 0) + if (id == settings::ui::SettingId::McForwardProfile) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_forward_profile = @@ -3657,21 +3605,21 @@ static void on_option_clicked(lv_event_t* e) app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "mc_channel_slot") == 0) + if (id == settings::ui::SettingId::McChannelSlot) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_channel_slot = static_cast(payload->value); app_ctx.saveConfig(); app_ctx.applyMeshConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "privacy_encrypt") == 0) + if (id == settings::ui::SettingId::PrivacyEncrypt) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().privacy_encrypt_mode = static_cast(payload->value); app_ctx.saveConfig(); app_ctx.applyPrivacyConfig(); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "external_nmea") == 0) + if (id == settings::ui::SettingId::ExternalNmea) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().external_nmea_output_hz = static_cast(payload->value); @@ -3679,7 +3627,7 @@ static void on_option_clicked(lv_event_t* e) gps_runtime::set_external_nmea_config(app_ctx.getConfig().external_nmea_output_hz, app_ctx.getConfig().external_nmea_sentence_mask); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "external_nmea_sent") == 0) + if (id == settings::ui::SettingId::ExternalNmeaSent) { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().external_nmea_sentence_mask = static_cast(payload->value); @@ -3687,7 +3635,7 @@ static void on_option_clicked(lv_event_t* e) gps_runtime::set_external_nmea_config(app_ctx.getConfig().external_nmea_output_hz, app_ctx.getConfig().external_nmea_sentence_mask); } - if (payload->item->pref_key && strcmp(payload->item->pref_key, "timezone_profile") == 0) + if (id == settings::ui::SettingId::TimezoneProfile) { ::platform::ui::time::set_timezone_profile_id(payload->value); g_settings.timezone_profile_id = ::platform::ui::time::timezone_profile_id(); @@ -4203,7 +4151,7 @@ static void open_option_modal(const settings::ui::SettingItem& item, settings::u { return; } - if (item.pref_key && std::strcmp(item.pref_key, "display_locale") == 0) + if (item_id(item) == settings::ui::SettingId::DisplayLocale) { refresh_language_pack_options(); update_item_value(widget); @@ -4560,38 +4508,30 @@ static const settings::ui::SettingOption kSpeakerVolumeOptions[] = { {"100%", 100}, }; -static settings::ui::SettingItem kGpsItems[] = { - {"GPS Enabled", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.gps_enabled, nullptr, 0, false, "gps_enabled"}, - {"Receiver Baud", settings::ui::SettingType::Enum, kGpsInitBaudOptions, 7, &g_settings.gps_init_baud, nullptr, nullptr, 0, false, "gps_init_baud"}, - {"Probe Window", settings::ui::SettingType::Enum, kGpsInitProbeOptions, 4, &g_settings.gps_init_probe_ms, nullptr, nullptr, 0, false, "gps_init_probe_ms"}, - {"Receiver Profile", settings::ui::SettingType::Enum, kGpsInitProfileOptions, 4, &g_settings.gps_init_profile, nullptr, nullptr, 0, false, "gps_init_profile"}, - {"RXM Init", settings::ui::SettingType::Enum, kGpsInitPolicyOptions, 3, &g_settings.gps_init_rxm_policy, nullptr, nullptr, 0, false, "gps_init_rxm"}, - {"GNSS Init", settings::ui::SettingType::Enum, kGpsInitPolicyOptions, 3, &g_settings.gps_init_gnss_policy, nullptr, nullptr, 0, false, "gps_init_gnss"}, - {"NMEA Init", settings::ui::SettingType::Enum, kGpsInitPolicyOptions, 3, &g_settings.gps_init_nmea_policy, nullptr, nullptr, 0, false, "gps_init_nmea"}, - {"Location Mode", settings::ui::SettingType::Enum, kGpsModeOptions, 3, &g_settings.gps_mode, nullptr, nullptr, 0, false, "gps_mode"}, - {"Satellite Systems", settings::ui::SettingType::Enum, kGpsSatOptions, 5, &g_settings.gps_sat_mask, nullptr, nullptr, 0, false, "gps_sat_mask"}, - {"Position Strategy", settings::ui::SettingType::Enum, kGpsStrategyOptions, 3, &g_settings.gps_strategy, nullptr, nullptr, 0, false, "gps_strategy"}, - {"Update Interval", settings::ui::SettingType::Enum, kGpsIntervalOptions, 4, &g_settings.gps_interval, nullptr, nullptr, 0, false, "gps_interval"}, - {"Altitude Reference", settings::ui::SettingType::Enum, kGpsAltOptions, 2, &g_settings.gps_alt_ref, nullptr, nullptr, 0, false, "gps_alt_ref"}, - {"Coordinate Format", settings::ui::SettingType::Enum, kGpsCoordOptions, 3, &g_settings.gps_coord_format, nullptr, nullptr, 0, false, "gps_coord_fmt"}, - {"NMEA Export", settings::ui::SettingType::Enum, kExternalNmeaOptions, 3, &g_settings.external_nmea_output_hz, nullptr, nullptr, 0, false, "external_nmea"}, - {"NMEA Sentences", settings::ui::SettingType::Enum, kExternalNmeaSentenceOptions, 3, &g_settings.external_nmea_sentence_mask, nullptr, nullptr, 0, false, "external_nmea_sent"}, - {"Diagnostics", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "gps_diagnostics"}, -}; - -static settings::ui::SettingItem kMapItems[] = { - {"Coordinate System", settings::ui::SettingType::Enum, kMapCoordOptions, 3, &g_settings.map_coord_system, nullptr, nullptr, 0, false, "map_coord"}, - {"Map Source", settings::ui::SettingType::Enum, kMapSourceOptions, 3, &g_settings.map_source, nullptr, nullptr, 0, false, "map_source"}, - {"Contour Overlay", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.map_contour_enabled, nullptr, 0, false, "map_contour"}, - {"Track Recording", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.map_track_enabled, nullptr, 0, false, "map_track"}, - {"Track Interval", settings::ui::SettingType::Enum, kMapTrackIntervalOptions, 4, &g_settings.map_track_interval, nullptr, nullptr, 0, false, "map_track_interval"}, - {"Track Format", settings::ui::SettingType::Enum, kMapTrackFormatOptions, 3, &g_settings.map_track_format, nullptr, nullptr, 0, false, "map_track_format"}, -}; - -static settings::ui::SettingItem kChatItems[] = { +static settings::ui::SettingItem kProfileItems[] = { {"User Name", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.user_name, sizeof(g_settings.user_name), false, "chat_user"}, {"Short Name", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.short_name, sizeof(g_settings.short_name), false, "chat_short"}, {"Protocol", settings::ui::SettingType::Enum, kChatProtocolOptions, 3, &g_settings.chat_protocol, nullptr, nullptr, 0, false, "mesh_protocol"}, + {"Message Alerts", settings::ui::SettingType::Enum, kBoolOptions, sizeof(kBoolOptions) / sizeof(kBoolOptions[0]), &g_settings.chat_message_alerts, nullptr, nullptr, 0, false, "chat_message_alerts"}, + {"Contact Alerts", settings::ui::SettingType::Enum, kChatContactAlertOptions, sizeof(kChatContactAlertOptions) / sizeof(kChatContactAlertOptions[0]), &g_settings.chat_contact_alerts, nullptr, nullptr, 0, false, "chat_contact_alerts"}, +}; + +static settings::ui::SettingItem kMeshItems[] = { + {"Region", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.chat_region, nullptr, nullptr, 0, false, "chat_region"}, + {"Active Chat Channel", settings::ui::SettingType::Enum, kChatChannelOptions, 2, &g_settings.chat_channel, nullptr, nullptr, 0, false, "chat_channel"}, + {"MT Primary Enabled", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mt_primary_enabled, nullptr, 0, false, "mt_primary_enabled"}, + {"MT Primary Name", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mt_primary_name, sizeof(g_settings.mt_primary_name), false, "mt_primary_name"}, + {"MT Primary PSK", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mt_primary_key, sizeof(g_settings.mt_primary_key), true, "mt_primary_key"}, + {"Generate MT Primary PSK", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "mt_primary_key_generate"}, + {"MT Primary Uplink", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mt_primary_uplink, nullptr, 0, false, "mt_primary_uplink"}, + {"MT Primary Downlink", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mt_primary_downlink, nullptr, 0, false, "mt_primary_downlink"}, + {"MT Secondary Enabled", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mt_secondary_enabled, nullptr, 0, false, "mt_secondary_enabled"}, + {"MT Secondary Name", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mt_secondary_name, sizeof(g_settings.mt_secondary_name), false, "mt_secondary_name"}, + {"MT Secondary PSK", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mt_secondary_key, sizeof(g_settings.mt_secondary_key), true, "mt_secondary_key"}, + {"Generate MT Secondary PSK", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "mt_secondary_key_generate"}, + {"MT Secondary Uplink", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mt_secondary_uplink, nullptr, 0, false, "mt_secondary_uplink"}, + {"MT Secondary Downlink", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mt_secondary_downlink, nullptr, 0, false, "mt_secondary_downlink"}, + {"Encryption Mode", settings::ui::SettingType::Enum, kPrivacyEncryptOptions, 3, &g_settings.privacy_encrypt_mode, nullptr, nullptr, 0, false, "privacy_encrypt"}, {"MT MQTT", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mt_mqtt_enabled, nullptr, 0, false, "mt_mqtt_enabled"}, {"MT MQTT Host", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mt_mqtt_host, sizeof(g_settings.mt_mqtt_host), false, "mt_mqtt_host"}, {"MT MQTT Port", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mt_mqtt_port, sizeof(g_settings.mt_mqtt_port), false, "mt_mqtt_port"}, @@ -4608,18 +4548,21 @@ static settings::ui::SettingItem kChatItems[] = { {"MC MQTT Pass", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_mqtt_pass, sizeof(g_settings.mc_mqtt_pass), true, "mc_mqtt_pass"}, {"MC MQTT Uplink", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mc_mqtt_uplink, nullptr, 0, false, "mc_mqtt_uplink"}, {"MC MQTT Downlink", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mc_mqtt_downlink, nullptr, 0, false, "mc_mqtt_downlink"}, - {"Region", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.chat_region, nullptr, nullptr, 0, false, "chat_region"}, - {"Channel", settings::ui::SettingType::Enum, kChatChannelOptions, 2, &g_settings.chat_channel, nullptr, nullptr, 0, false, "chat_channel"}, - {"Channel Key / PSK", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.chat_psk, sizeof(g_settings.chat_psk), true, "chat_psk"}, - {"Encryption Mode", settings::ui::SettingType::Enum, kPrivacyEncryptOptions, 3, &g_settings.privacy_encrypt_mode, nullptr, nullptr, 0, false, "privacy_encrypt"}, - {"Message Alerts", settings::ui::SettingType::Enum, kBoolOptions, sizeof(kBoolOptions) / sizeof(kBoolOptions[0]), &g_settings.chat_message_alerts, nullptr, nullptr, 0, false, "chat_message_alerts"}, - {"Contact Alerts", settings::ui::SettingType::Enum, kChatContactAlertOptions, sizeof(kChatContactAlertOptions) / sizeof(kChatContactAlertOptions[0]), &g_settings.chat_contact_alerts, nullptr, nullptr, 0, false, "chat_contact_alerts"}, - {"Reset Mesh Profiles", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "chat_reset_mesh"}, - {"Reset Node DB", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "chat_reset_nodes"}, - {"Clear Message DB", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "chat_clear_messages"}, + {"MC Channel Slot", settings::ui::SettingType::Enum, kMeshCoreChannelSlotOptions, sizeof(kMeshCoreChannelSlotOptions) / sizeof(kMeshCoreChannelSlotOptions[0]), &g_settings.mc_channel_slot, nullptr, nullptr, 0, false, "mc_channel_slot"}, + {"MC Channel Name", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_channel_name, sizeof(g_settings.mc_channel_name), false, "mc_channel_name"}, + {"MC Channel Key", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_channel_key, sizeof(g_settings.mc_channel_key), true, "mc_channel_key"}, + {"Generate MC Channel Key", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "mc_channel_key_generate"}, + {"Bearer", settings::ui::SettingType::Enum, kReticulumBearerOptions, sizeof(kReticulumBearerOptions) / sizeof(kReticulumBearerOptions[0]), &g_settings.rt_bearer_policy, nullptr, nullptr, 0, false, "rt_bearer"}, + {"Display Name", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.rt_display_name, sizeof(g_settings.rt_display_name), false, "rt_display_name"}, + {"Identity Hash", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.rt_identity_hash, sizeof(g_settings.rt_identity_hash), false, "rt_identity_hash"}, + {"LXMF Address", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.rt_lxmf_address, sizeof(g_settings.rt_lxmf_address), false, "rt_lxmf_address"}, + {"Gateway Host", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.rt_wifi_gateway_host, sizeof(g_settings.rt_wifi_gateway_host), false, "rt_wifi_host"}, + {"Gateway Port", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.rt_wifi_gateway_port, sizeof(g_settings.rt_wifi_gateway_port), false, "rt_wifi_port"}, + {"Auto Wi-Fi", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.rt_wifi_auto_connect, nullptr, 0, false, "rt_wifi_auto"}, + {"Anonymous Peer", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.rt_anonymous_peer, nullptr, 0, false, "rt_anonymous_peer"}, }; -static settings::ui::SettingItem kNetworkItems[] = { +static settings::ui::SettingItem kRadioItems[] = { {"Use Preset", settings::ui::SettingType::Enum, kBoolOptions, sizeof(kBoolOptions) / sizeof(kBoolOptions[0]), &g_settings.net_use_preset, nullptr, nullptr, 0, false, "net_use_preset"}, {"Modem Preset", settings::ui::SettingType::Enum, kNetPresetOptions, sizeof(kNetPresetOptions) / sizeof(kNetPresetOptions[0]), &g_settings.net_modem_preset, nullptr, nullptr, 0, false, "net_preset"}, {"Manual BW", settings::ui::SettingType::Enum, kNetManualBwOptions, sizeof(kNetManualBwOptions) / sizeof(kNetManualBwOptions[0]), &g_settings.net_manual_bw, nullptr, nullptr, 0, false, "net_bw"}, @@ -4629,14 +4572,6 @@ static settings::ui::SettingItem kNetworkItems[] = { 0, &g_settings.net_tx_power, nullptr, nullptr, 0, false, "net_tx_power"}, {"Hop Limit", settings::ui::SettingType::Enum, kHopLimitOptions, sizeof(kHopLimitOptions) / sizeof(kHopLimitOptions[0]), &g_settings.net_hop_limit, nullptr, nullptr, 0, false, "net_hop_limit"}, {"TX Enabled", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.net_tx_enabled, nullptr, 0, false, "net_tx_enabled"}, - {"Bearer", settings::ui::SettingType::Enum, kReticulumBearerOptions, sizeof(kReticulumBearerOptions) / sizeof(kReticulumBearerOptions[0]), &g_settings.rt_bearer_policy, nullptr, nullptr, 0, false, "rt_bearer"}, - {"Display Name", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.rt_display_name, sizeof(g_settings.rt_display_name), false, "rt_display_name"}, - {"Identity Hash", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.rt_identity_hash, sizeof(g_settings.rt_identity_hash), false, "rt_identity_hash"}, - {"LXMF Address", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.rt_lxmf_address, sizeof(g_settings.rt_lxmf_address), false, "rt_lxmf_address"}, - {"Gateway Host", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.rt_wifi_gateway_host, sizeof(g_settings.rt_wifi_gateway_host), false, "rt_wifi_host"}, - {"Gateway Port", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.rt_wifi_gateway_port, sizeof(g_settings.rt_wifi_gateway_port), false, "rt_wifi_port"}, - {"Auto Wi-Fi", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.rt_wifi_auto_connect, nullptr, 0, false, "rt_wifi_auto"}, - {"Anonymous Peer", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.rt_anonymous_peer, nullptr, 0, false, "rt_anonymous_peer"}, {"Override Duty Cycle", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.net_override_duty_cycle, nullptr, 0, false, "net_override_duty"}, {"Channel Slot", settings::ui::SettingType::Enum, kChannelNumOptions, sizeof(kChannelNumOptions) / sizeof(kChannelNumOptions[0]), &g_settings.net_channel_num, nullptr, nullptr, 0, false, "net_channel_num"}, {"Freq Offset (MHz)", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.net_freq_offset, sizeof(g_settings.net_freq_offset), false, "net_freq_offset"}, @@ -4654,14 +4589,36 @@ static settings::ui::SettingItem kNetworkItems[] = { {"MC Multi ACKs", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mc_multi_acks, nullptr, 0, false, "mc_multi_acks"}, {"MC Send Profile", settings::ui::SettingType::Enum, kMeshCoreSendProfileOptions, sizeof(kMeshCoreSendProfileOptions) / sizeof(kMeshCoreSendProfileOptions[0]), &g_settings.mc_send_profile, nullptr, nullptr, 0, false, "mc_send_prof"}, {"MC Forward Profile", settings::ui::SettingType::Enum, kMeshCoreForwardProfileOptions, sizeof(kMeshCoreForwardProfileOptions) / sizeof(kMeshCoreForwardProfileOptions[0]), &g_settings.mc_forward_profile, nullptr, nullptr, 0, false, "mc_fwd_prof"}, - {"MC Channel Slot", settings::ui::SettingType::Enum, kMeshCoreChannelSlotOptions, sizeof(kMeshCoreChannelSlotOptions) / sizeof(kMeshCoreChannelSlotOptions[0]), &g_settings.mc_channel_slot, nullptr, nullptr, 0, false, "mc_channel_slot"}, - {"MC Channel Name", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_channel_name, sizeof(g_settings.mc_channel_name), false, "mc_channel_name"}, - {"MC Channel Key", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_channel_key, sizeof(g_settings.mc_channel_key), true, "mc_channel_key"}, {"Duty Cycle Limit", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.net_duty_cycle, nullptr, 0, false, "net_duty_cycle"}, {"Channel Utilization", settings::ui::SettingType::Enum, kNetUtilOptions, 3, &g_settings.net_channel_util, nullptr, nullptr, 0, false, "net_util"}, }; -static settings::ui::SettingItem kScreenItems[] = { +static settings::ui::SettingItem kLocationItems[] = { + {"GPS Enabled", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.gps_enabled, nullptr, 0, false, "gps_enabled"}, + {"Receiver Baud", settings::ui::SettingType::Enum, kGpsInitBaudOptions, 7, &g_settings.gps_init_baud, nullptr, nullptr, 0, false, "gps_init_baud"}, + {"Probe Window", settings::ui::SettingType::Enum, kGpsInitProbeOptions, 4, &g_settings.gps_init_probe_ms, nullptr, nullptr, 0, false, "gps_init_probe_ms"}, + {"Receiver Profile", settings::ui::SettingType::Enum, kGpsInitProfileOptions, 4, &g_settings.gps_init_profile, nullptr, nullptr, 0, false, "gps_init_profile"}, + {"RXM Init", settings::ui::SettingType::Enum, kGpsInitPolicyOptions, 3, &g_settings.gps_init_rxm_policy, nullptr, nullptr, 0, false, "gps_init_rxm"}, + {"GNSS Init", settings::ui::SettingType::Enum, kGpsInitPolicyOptions, 3, &g_settings.gps_init_gnss_policy, nullptr, nullptr, 0, false, "gps_init_gnss"}, + {"NMEA Init", settings::ui::SettingType::Enum, kGpsInitPolicyOptions, 3, &g_settings.gps_init_nmea_policy, nullptr, nullptr, 0, false, "gps_init_nmea"}, + {"Location Mode", settings::ui::SettingType::Enum, kGpsModeOptions, 3, &g_settings.gps_mode, nullptr, nullptr, 0, false, "gps_mode"}, + {"Satellite Systems", settings::ui::SettingType::Enum, kGpsSatOptions, 5, &g_settings.gps_sat_mask, nullptr, nullptr, 0, false, "gps_sat_mask"}, + {"Position Strategy", settings::ui::SettingType::Enum, kGpsStrategyOptions, 3, &g_settings.gps_strategy, nullptr, nullptr, 0, false, "gps_strategy"}, + {"Update Interval", settings::ui::SettingType::Enum, kGpsIntervalOptions, 4, &g_settings.gps_interval, nullptr, nullptr, 0, false, "gps_interval"}, + {"Altitude Reference", settings::ui::SettingType::Enum, kGpsAltOptions, 2, &g_settings.gps_alt_ref, nullptr, nullptr, 0, false, "gps_alt_ref"}, + {"Coordinate Format", settings::ui::SettingType::Enum, kGpsCoordOptions, 3, &g_settings.gps_coord_format, nullptr, nullptr, 0, false, "gps_coord_fmt"}, + {"NMEA Export", settings::ui::SettingType::Enum, kExternalNmeaOptions, 3, &g_settings.external_nmea_output_hz, nullptr, nullptr, 0, false, "external_nmea"}, + {"NMEA Sentences", settings::ui::SettingType::Enum, kExternalNmeaSentenceOptions, 3, &g_settings.external_nmea_sentence_mask, nullptr, nullptr, 0, false, "external_nmea_sent"}, + {"Diagnostics", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "gps_diagnostics"}, + {"Coordinate System", settings::ui::SettingType::Enum, kMapCoordOptions, 3, &g_settings.map_coord_system, nullptr, nullptr, 0, false, "map_coord"}, + {"Map Source", settings::ui::SettingType::Enum, kMapSourceOptions, 3, &g_settings.map_source, nullptr, nullptr, 0, false, "map_source"}, + {"Contour Overlay", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.map_contour_enabled, nullptr, 0, false, "map_contour"}, + {"Track Recording", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.map_track_enabled, nullptr, 0, false, "map_track"}, + {"Track Interval", settings::ui::SettingType::Enum, kMapTrackIntervalOptions, 4, &g_settings.map_track_interval, nullptr, nullptr, 0, false, "map_track_interval"}, + {"Track Format", settings::ui::SettingType::Enum, kMapTrackFormatOptions, 3, &g_settings.map_track_format, nullptr, nullptr, 0, false, "map_track_format"}, +}; + +static settings::ui::SettingItem kDeviceItems[] = { {"Display Language", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.display_locale_index, nullptr, nullptr, 0, false, "display_locale"}, @@ -4672,7 +4629,6 @@ static settings::ui::SettingItem kScreenItems[] = { {"Speaker Volume", settings::ui::SettingType::Enum, kSpeakerVolumeOptions, sizeof(kSpeakerVolumeOptions) / sizeof(kSpeakerVolumeOptions[0]), &g_settings.speaker_volume, nullptr, nullptr, 0, false, "speaker_volume"}, {"Vibration", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.vibration_enabled, nullptr, 0, false, "vibration_enabled"}, - {"Bluetooth", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.ble_enabled, nullptr, 0, false, "ble_enabled"}, {"C6 Companion", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.c6_companion_status, sizeof(g_settings.c6_companion_status), false, "c6_companion_status"}, {"C6 Download Mode", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "c6_enter_download"}, @@ -4682,7 +4638,6 @@ static settings::ui::SettingItem kScreenItems[] = { g_settings.gauge_design_mah, sizeof(g_settings.gauge_design_mah), false, "gauge_design_mah"}, {"Gauge Full (mAh)", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.gauge_full_mah, sizeof(g_settings.gauge_full_mah), false, "gauge_full_mah"}, - {"Factory Reset", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "system_factory_reset"}, }; static settings::ui::SettingItem kWifiItems[] = { @@ -4696,7 +4651,7 @@ static settings::ui::SettingItem kWifiItems[] = { {"Disconnect", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "wifi_disconnect"}, }; -static settings::ui::SettingItem kAdvancedItems[] = { +static settings::ui::SettingItem kMaintenanceItems[] = { {"Current Version", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.fw_current_version, sizeof(g_settings.fw_current_version), false, "fw_current"}, {"Latest Version", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.fw_latest_version, sizeof(g_settings.fw_latest_version), false, "fw_latest"}, {"OTA Status", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.fw_update_status, sizeof(g_settings.fw_update_status), false, "fw_status"}, @@ -4706,16 +4661,20 @@ static settings::ui::SettingItem kAdvancedItems[] = { {"Backup Settings", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "settings_backup"}, {"Restore Settings", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "settings_restore"}, {"Debug Logs", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.advanced_debug_logs, nullptr, 0, false, "adv_debug"}, + {"Reset Mesh Profiles", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "chat_reset_mesh"}, + {"Reset Node DB", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "chat_reset_nodes"}, + {"Clear Message DB", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "chat_clear_messages"}, + {"Factory Reset", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "system_factory_reset"}, }; static const CategoryDef kCategories[] = { - {"GPS", kGpsItems, sizeof(kGpsItems) / sizeof(kGpsItems[0])}, - {"Map", kMapItems, sizeof(kMapItems) / sizeof(kMapItems[0])}, - {"Chat", kChatItems, sizeof(kChatItems) / sizeof(kChatItems[0])}, - {"Network", kNetworkItems, sizeof(kNetworkItems) / sizeof(kNetworkItems[0])}, - {"System", kScreenItems, sizeof(kScreenItems) / sizeof(kScreenItems[0])}, + {"Profile", kProfileItems, sizeof(kProfileItems) / sizeof(kProfileItems[0])}, + {"Mesh", kMeshItems, sizeof(kMeshItems) / sizeof(kMeshItems[0])}, + {"Radio", kRadioItems, sizeof(kRadioItems) / sizeof(kRadioItems[0])}, {"Wi-Fi", kWifiItems, sizeof(kWifiItems) / sizeof(kWifiItems[0])}, - {"Advanced", kAdvancedItems, sizeof(kAdvancedItems) / sizeof(kAdvancedItems[0])}, + {"Location", kLocationItems, sizeof(kLocationItems) / sizeof(kLocationItems[0])}, + {"Device", kDeviceItems, sizeof(kDeviceItems) / sizeof(kDeviceItems[0])}, + {"Maintenance", kMaintenanceItems, sizeof(kMaintenanceItems) / sizeof(kMaintenanceItems[0])}, }; static void update_filter_styles() @@ -4788,50 +4747,26 @@ static bool select_filter_index(int idx) return true; } -static bool has_pref_key(const settings::ui::SettingItem& item, const char* key) +static settings::ui::SettingId item_id(const settings::ui::SettingItem& item) { - return item.pref_key && key && strcmp(item.pref_key, key) == 0; -} - -static bool is_meshtastic_mqtt_setting(const settings::ui::SettingItem& item) -{ - return has_pref_key(item, "mt_mqtt_enabled") || - has_pref_key(item, "mt_mqtt_host") || - has_pref_key(item, "mt_mqtt_port") || - has_pref_key(item, "mt_mqtt_root") || - has_pref_key(item, "mt_mqtt_user") || - has_pref_key(item, "mt_mqtt_pass") || - has_pref_key(item, "mt_mqtt_uplink") || - has_pref_key(item, "mt_mqtt_downlink"); -} - -static bool is_meshcore_mqtt_setting(const settings::ui::SettingItem& item) -{ - return has_pref_key(item, "mc_mqtt_enabled") || - has_pref_key(item, "mc_mqtt_host") || - has_pref_key(item, "mc_mqtt_port") || - has_pref_key(item, "mc_mqtt_root") || - has_pref_key(item, "mc_mqtt_user") || - has_pref_key(item, "mc_mqtt_pass") || - has_pref_key(item, "mc_mqtt_uplink") || - has_pref_key(item, "mc_mqtt_downlink"); + return item.id != settings::ui::SettingId::Unknown + ? item.id + : ::settings::ui::spec::id_for_key(item.pref_key); } static bool option_labels_are_translated(const settings::ui::SettingItem& item) { - return !has_pref_key(item, "display_locale") && - !has_pref_key(item, "wifi_network"); + return ::settings::ui::spec::option_labels_are_translated(item_id(item)); } static bool option_labels_use_content_font(const settings::ui::SettingItem& item) { - return has_pref_key(item, "display_locale") || - has_pref_key(item, "wifi_network"); + return ::settings::ui::spec::option_labels_use_content_font(item_id(item)); } static const ::ui::i18n::LocaleInfo* locale_info_for_option_value(const settings::ui::SettingItem& item, int value) { - if (!has_pref_key(item, "display_locale") || value < 0) + if (item_id(item) != settings::ui::SettingId::DisplayLocale || value < 0) { return nullptr; } @@ -4862,231 +4797,28 @@ static void apply_locale_preview_font(lv_obj_t* label, const settings::ui::Setti static bool should_show_item(const settings::ui::SettingItem& item) { - if (!item.pref_key) - { - return true; - } - - if (has_pref_key(item, "wifi_network")) - { - return wifi_runtime::is_supported() && kWifiNetworkOptionCount > 0; - } - if ((has_pref_key(item, "wifi_scan") || has_pref_key(item, "wifi_connect") || - has_pref_key(item, "wifi_disconnect") || has_pref_key(item, "wifi_ssid") || - has_pref_key(item, "wifi_password") || has_pref_key(item, "wifi_enabled")) && - !wifi_runtime::is_supported()) - { - return false; - } - if ((has_pref_key(item, "fw_current") || has_pref_key(item, "fw_latest") || - has_pref_key(item, "fw_status") || has_pref_key(item, "fw_check") || - has_pref_key(item, "fw_install")) && - !firmware_update_runtime::is_supported()) - { - return false; - } - if ((has_pref_key(item, "settings_backup_status") || - has_pref_key(item, "settings_backup") || - has_pref_key(item, "settings_restore")) && - !settings_backup_runtime::is_supported()) - { - return false; - } - if ((has_pref_key(item, "c6_companion_status") || - has_pref_key(item, "c6_enter_download")) && - !wireless_companion_runtime::is_supported()) - { - return false; - } - - const bool meshtastic = is_meshtastic_protocol_selected(); - const bool meshcore = is_meshcore_protocol_selected(); - const bool reticulum = is_reticulum_protocol_selected(); - - if (is_meshtastic_mqtt_setting(item)) - { - if (!meshtastic) - { - return false; - } - } - - if (is_meshcore_mqtt_setting(item)) - { - if (!meshcore) - { - return false; - } - } - - // Relay is currently not implemented as real forwarding in Meshtastic path. - if (has_pref_key(item, "net_relay")) - { - return false; - } - - if (has_pref_key(item, "screen_brightness") && !device_runtime::supports_screen_brightness()) - { - return false; - } - if (has_pref_key(item, "screen_timeout") && !screen_runtime::supports_app_timeout_setting()) - { - return false; - } - if (has_pref_key(item, "gps_init_baud") && !gps_runtime::supports_receiver_baud_setting()) - { - return false; - } - if ((has_pref_key(item, "gps_init_probe_ms") || - has_pref_key(item, "gps_init_profile") || - has_pref_key(item, "gps_init_rxm") || - has_pref_key(item, "gps_init_gnss") || - has_pref_key(item, "gps_init_nmea")) && - !gps_runtime::supports_receiver_init_policy_settings()) - { - return false; - } - if ((has_pref_key(item, "gps_mode") || - has_pref_key(item, "gps_sat_mask") || - has_pref_key(item, "gps_strategy")) && - !gps_runtime::supports_gnss_runtime_settings()) - { - return false; - } - if (has_pref_key(item, "gps_interval") && !gps_runtime::supports_collection_interval_setting()) - { - return false; - } - if (has_pref_key(item, "gps_alt_ref") && !gps_runtime::supports_altitude_reference_setting()) - { - return false; - } - if (has_pref_key(item, "gps_coord_fmt") && !gps_runtime::supports_coordinate_format_setting()) - { - return false; - } - if ((has_pref_key(item, "external_nmea") || - has_pref_key(item, "external_nmea_sent")) && - !gps_runtime::supports_external_nmea_output_setting()) - { - return false; - } - if ((has_pref_key(item, "gauge_design_mah") || has_pref_key(item, "gauge_full_mah")) && - !device_runtime::supports_configurable_battery_gauge()) - { - return false; - } - if (meshcore) - { - if (has_pref_key(item, "chat_region")) return false; - if (has_pref_key(item, "chat_channel")) return false; - if (has_pref_key(item, "chat_psk")) return false; - - if (has_pref_key(item, "net_use_preset")) return false; - if (has_pref_key(item, "net_preset")) return false; - if (has_pref_key(item, "net_bw")) return false; - if (has_pref_key(item, "net_sf")) return false; - if (has_pref_key(item, "net_cr")) return false; - if (has_pref_key(item, "net_tx_power")) return false; - if (has_pref_key(item, "net_hop_limit")) return false; - if (has_pref_key(item, "rt_bearer")) return false; - if (has_pref_key(item, "rt_lora_enabled")) return false; - if (has_pref_key(item, "rt_display_name")) return false; - if (has_pref_key(item, "rt_identity_hash")) return false; - if (has_pref_key(item, "rt_lxmf_address")) return false; - if (has_pref_key(item, "rt_wifi_gateway")) return false; - if (has_pref_key(item, "rt_wifi_host")) return false; - if (has_pref_key(item, "rt_wifi_port")) return false; - if (has_pref_key(item, "rt_wifi_auto")) return false; - if (has_pref_key(item, "rt_anonymous_peer")) return false; - if (has_pref_key(item, "net_override_duty")) return false; - if (has_pref_key(item, "net_channel_num")) return false; - if (has_pref_key(item, "net_freq_offset")) return false; - if (has_pref_key(item, "net_override_freq")) return false; - } - else if (reticulum) - { - if (has_pref_key(item, "chat_region")) return false; - if (has_pref_key(item, "chat_channel")) return false; - if (has_pref_key(item, "chat_psk")) return false; - if (has_pref_key(item, "privacy_encrypt")) return false; - - if (has_pref_key(item, "net_use_preset")) return false; - if (has_pref_key(item, "net_preset")) return false; - if (has_pref_key(item, "net_hop_limit")) return false; - if (has_pref_key(item, "net_override_duty")) return false; - if (has_pref_key(item, "net_channel_num")) return false; - if (has_pref_key(item, "net_freq_offset")) return false; - if (has_pref_key(item, "net_duty_cycle")) return false; - if (has_pref_key(item, "net_util")) return false; - - if (has_pref_key(item, "mc_region_preset")) return false; - if (has_pref_key(item, "mc_freq")) return false; - if (has_pref_key(item, "mc_bw")) return false; - if (has_pref_key(item, "mc_sf")) return false; - if (has_pref_key(item, "mc_cr")) return false; - if (has_pref_key(item, "mc_tx_power")) return false; - if (has_pref_key(item, "mc_repeat")) return false; - if (has_pref_key(item, "mc_rx_delay")) return false; - if (has_pref_key(item, "mc_airtime")) return false; - if (has_pref_key(item, "mc_flood_max")) return false; - if (has_pref_key(item, "mc_multi_acks")) return false; - if (has_pref_key(item, "mc_send_prof")) return false; - if (has_pref_key(item, "mc_fwd_prof")) return false; - if (has_pref_key(item, "mc_channel_slot")) return false; - if (has_pref_key(item, "mc_channel_name")) return false; - if (has_pref_key(item, "mc_channel_key")) return false; - if (has_pref_key(item, "rt_lora_enabled")) return false; - if (has_pref_key(item, "rt_wifi_gateway")) return false; - if ((has_pref_key(item, "rt_wifi_host") || - has_pref_key(item, "rt_wifi_port") || - has_pref_key(item, "rt_wifi_auto")) && - !reticulum_wifi_settings_visible()) - { - return false; - } - } - else - { - if (has_pref_key(item, "mc_region_preset")) return false; - if (has_pref_key(item, "mc_freq")) return false; - if (has_pref_key(item, "mc_bw")) return false; - if (has_pref_key(item, "mc_sf")) return false; - if (has_pref_key(item, "mc_cr")) return false; - if (has_pref_key(item, "mc_tx_power")) return false; - if (has_pref_key(item, "mc_repeat")) return false; - if (has_pref_key(item, "mc_rx_delay")) return false; - if (has_pref_key(item, "mc_airtime")) return false; - if (has_pref_key(item, "mc_flood_max")) return false; - if (has_pref_key(item, "mc_multi_acks")) return false; - if (has_pref_key(item, "mc_send_prof")) return false; - if (has_pref_key(item, "mc_fwd_prof")) return false; - if (has_pref_key(item, "mc_channel_slot")) return false; - if (has_pref_key(item, "mc_channel_name")) return false; - if (has_pref_key(item, "mc_channel_key")) return false; - - if (has_pref_key(item, "rt_bearer")) return false; - if (has_pref_key(item, "rt_lora_enabled")) return false; - if (has_pref_key(item, "rt_display_name")) return false; - if (has_pref_key(item, "rt_identity_hash")) return false; - if (has_pref_key(item, "rt_lxmf_address")) return false; - if (has_pref_key(item, "rt_wifi_gateway")) return false; - if (has_pref_key(item, "rt_wifi_host")) return false; - if (has_pref_key(item, "rt_wifi_port")) return false; - if (has_pref_key(item, "rt_wifi_auto")) return false; - if (has_pref_key(item, "rt_anonymous_peer")) return false; - - if (has_pref_key(item, "net_preset")) - { - return g_settings.net_use_preset; - } - if (has_pref_key(item, "net_bw") || has_pref_key(item, "net_sf") || has_pref_key(item, "net_cr")) - { - return !g_settings.net_use_preset; - } - } - - return true; + ::settings::ui::spec::VisibilityContext context{}; + context.protocol = selected_protocol(); + context.wifi_supported = wifi_runtime::is_supported(); + context.has_wifi_networks = kWifiNetworkOptionCount > 0; + context.firmware_update_supported = firmware_update_runtime::is_supported(); + context.settings_backup_supported = settings_backup_runtime::is_supported(); + context.wireless_companion_supported = wireless_companion_runtime::is_supported(); + context.mt_secondary_enabled = g_settings.mt_secondary_enabled; + context.mt_use_preset = g_settings.net_use_preset != 0; + context.reticulum_wifi_visible = reticulum_wifi_settings_visible(); + context.reticulum_lora_visible = reticulum_lora_settings_visible(); + context.screen_brightness_supported = device_runtime::supports_screen_brightness(); + context.screen_timeout_supported = screen_runtime::supports_app_timeout_setting(); + context.gps_baud_supported = gps_runtime::supports_receiver_baud_setting(); + context.gps_init_policy_supported = gps_runtime::supports_receiver_init_policy_settings(); + context.gps_gnss_supported = gps_runtime::supports_gnss_runtime_settings(); + context.gps_interval_supported = gps_runtime::supports_collection_interval_setting(); + context.gps_alt_ref_supported = gps_runtime::supports_altitude_reference_setting(); + context.gps_coord_format_supported = gps_runtime::supports_coordinate_format_setting(); + context.external_nmea_supported = gps_runtime::supports_external_nmea_output_setting(); + context.battery_gauge_supported = device_runtime::supports_configurable_battery_gauge(); + return ::settings::ui::spec::should_show(item_id(item), context); } static void list_item_focused_cb(lv_event_t* e) @@ -5128,28 +4860,34 @@ static void build_item_list() { settings::ui::ItemWidget& widget = g_state.item_widgets[g_state.item_count]; widget.def = &cat.items[i]; - if (widget.def && widget.def->pref_key && strcmp(widget.def->pref_key, "chat_region") == 0) + if (widget.def) { - const_cast(widget.def)->option_count = kChatRegionOptionCount; - } - if (widget.def && widget.def->pref_key && strcmp(widget.def->pref_key, "mc_region_preset") == 0) - { - const_cast(widget.def)->option_count = kMeshCoreRegionPresetOptionCount; - } - if (widget.def && widget.def->pref_key && - strcmp(widget.def->pref_key, "display_locale") == 0) - { - const_cast(widget.def)->option_count = kLocaleOptionCount; - } - if (widget.def && widget.def->pref_key && - strcmp(widget.def->pref_key, "wifi_network") == 0) - { - const_cast(widget.def)->option_count = kWifiNetworkOptionCount; - } - if (widget.def && widget.def->pref_key && - (strcmp(widget.def->pref_key, "net_tx_power") == 0 || strcmp(widget.def->pref_key, "mc_tx_power") == 0)) - { - const_cast(widget.def)->option_count = kTxPowerOptionCount; + settings::ui::SettingItem* mutable_def = + const_cast(widget.def); + switch (::settings::ui::spec::dynamic_option_kind(item_id(*widget.def))) + { + case ::settings::ui::spec::DynamicOptionKind::ChatRegion: + mutable_def->option_count = kChatRegionOptionCount; + break; + case ::settings::ui::spec::DynamicOptionKind::MeshCoreRegionPreset: + mutable_def->option_count = kMeshCoreRegionPresetOptionCount; + break; + case ::settings::ui::spec::DynamicOptionKind::Locale: + mutable_def->option_count = kLocaleOptionCount; + break; + case ::settings::ui::spec::DynamicOptionKind::TimeZone: + mutable_def->option_count = kTimeZoneOptionCount; + break; + case ::settings::ui::spec::DynamicOptionKind::WifiNetwork: + mutable_def->option_count = kWifiNetworkOptionCount; + break; + case ::settings::ui::spec::DynamicOptionKind::TxPower: + mutable_def->option_count = kTxPowerOptionCount; + break; + case ::settings::ui::spec::DynamicOptionKind::None: + default: + break; + } } if (!should_show_item(*widget.def)) { @@ -5213,6 +4951,38 @@ static void build_item_list() #endif } +static void generate_meshtastic_channel_key(bool primary) +{ + app::IAppFacade& app_ctx = app::appFacade(); + if (!::settings::ui::channel::generate_meshtastic_channel_key( + app_ctx.getConfig(), + g_settings, + primary)) + { + ::ui::feedback::show_notice(::ui::i18n::tr("PSK generation failed"), 3000); + return; + } + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + refresh_visible_item_values(); + ::ui::feedback::show_notice(::ui::i18n::tr("Channel PSK generated"), 2200); +} + +static void generate_meshcore_channel_key() +{ + app::IAppFacade& app_ctx = app::appFacade(); + if (!::settings::ui::channel::generate_meshcore_channel_key(app_ctx.getConfig(), + g_settings)) + { + ::ui::feedback::show_notice(::ui::i18n::tr("Key generation failed"), 3000); + return; + } + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + refresh_visible_item_values(); + ::ui::feedback::show_notice(::ui::i18n::tr("Channel key generated"), 2200); +} + static bool activate_item_widget(settings::ui::ItemWidget& widget) { if (!widget.def) @@ -5225,51 +4995,59 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) { return false; } + const settings::ui::SettingId id = item_id(item); if (item.type == settings::ui::SettingType::Toggle) { if (item.bool_value) { *item.bool_value = !(*item.bool_value); - if (is_settings_store_owned_toggle_setting(item.pref_key)) + if (::settings::ui::spec::is_settings_store_owned_toggle(id)) { prefs_put_bool(item.pref_key, *item.bool_value); } update_item_value(widget); - if (item.pref_key && strcmp(item.pref_key, "net_relay") == 0) + switch (id) + { + case settings::ui::SettingId::NetRelay: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_config.enable_relay = *item.bool_value; app_ctx.saveConfig(); app_ctx.applyMeshConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "map_track") == 0) + case settings::ui::SettingId::MapTrack: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().map_track_enabled = *item.bool_value; app_ctx.saveConfig(); apply_track_recording_runtime(*item.bool_value); + break; } - if (item.pref_key && strcmp(item.pref_key, "map_contour") == 0) + case settings::ui::SettingId::MapContour: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().map_contour_enabled = *item.bool_value; app_ctx.saveConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "gps_enabled") == 0) + case settings::ui::SettingId::GpsEnabled: { if (!apply_settings_bool_patch("gps_enabled", *item.bool_value)) { ::ui::feedback::show_notice(::ui::i18n::tr("Unable to apply GPS setting"), 3000); } + break; } - if (item.pref_key && strcmp(item.pref_key, "net_duty_cycle") == 0) + case settings::ui::SettingId::NetDutyCycle: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().net_duty_cycle = *item.bool_value; app_ctx.saveConfig(); app_ctx.applyNetworkLimits(); + break; } - if (item.pref_key && strcmp(item.pref_key, "net_tx_enabled") == 0) + case settings::ui::SettingId::NetTxEnabled: { app::IAppFacade& app_ctx = app::appFacade(); if (app_ctx.getConfig().mesh_protocol == chat::MeshProtocol::MeshCore) @@ -5286,89 +5064,143 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) } app_ctx.saveConfig(); app_ctx.applyMeshConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "rt_wifi_auto") == 0) + case settings::ui::SettingId::RtWifiAuto: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().reticulumConfig().reticulum_wifi_auto_connect = *item.bool_value; app_ctx.saveConfig(); app_ctx.applyMeshConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "rt_anonymous_peer") == 0) + case settings::ui::SettingId::RtAnonymousPeer: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().reticulumConfig().reticulum_anonymous_peer = *item.bool_value; app_ctx.saveConfig(); app_ctx.applyMeshConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "net_override_duty") == 0) + case settings::ui::SettingId::NetOverrideDuty: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_config.override_duty_cycle = *item.bool_value; app_ctx.saveConfig(); app_ctx.applyMeshConfig(); app_ctx.applyNetworkLimits(); + break; } - if (item.pref_key && strcmp(item.pref_key, "mc_repeat") == 0) + case settings::ui::SettingId::McRepeat: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_client_repeat = *item.bool_value; app_ctx.saveConfig(); app_ctx.applyMeshConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "mc_multi_acks") == 0) + case settings::ui::SettingId::McMultiAcks: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_multi_acks = *item.bool_value; app_ctx.saveConfig(); app_ctx.applyMeshConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "mt_mqtt_enabled") == 0) + case settings::ui::SettingId::MtPrimaryEnabled: + { + app::IAppFacade& app_ctx = app::appFacade(); + app_ctx.getConfig().primary_enabled = *item.bool_value; + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + break; + } + case settings::ui::SettingId::MtPrimaryUplink: + { + app::IAppFacade& app_ctx = app::appFacade(); + app_ctx.getConfig().primary_uplink_enabled = *item.bool_value; + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + break; + } + case settings::ui::SettingId::MtPrimaryDownlink: + { + app::IAppFacade& app_ctx = app::appFacade(); + app_ctx.getConfig().primary_downlink_enabled = *item.bool_value; + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + break; + } + case settings::ui::SettingId::MtSecondaryEnabled: + { + app::IAppFacade& app_ctx = app::appFacade(); + app_ctx.getConfig().secondary_enabled = *item.bool_value; + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + build_item_list(); + break; + } + case settings::ui::SettingId::MtSecondaryUplink: + { + app::IAppFacade& app_ctx = app::appFacade(); + app_ctx.getConfig().secondary_uplink_enabled = *item.bool_value; + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + break; + } + case settings::ui::SettingId::MtSecondaryDownlink: + { + app::IAppFacade& app_ctx = app::appFacade(); + app_ctx.getConfig().secondary_downlink_enabled = *item.bool_value; + app_ctx.saveConfig(); + app_ctx.applyMeshConfig(); + break; + } + case settings::ui::SettingId::MtMqttEnabled: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_mqtt_enabled = *item.bool_value; app_ctx.saveConfig(); build_item_list(); + break; } - if (item.pref_key && strcmp(item.pref_key, "mt_mqtt_uplink") == 0) + case settings::ui::SettingId::MtMqttUplink: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_mqtt_uplink_enabled = *item.bool_value; app_ctx.saveConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "mt_mqtt_downlink") == 0) + case settings::ui::SettingId::MtMqttDownlink: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshtastic_mqtt_downlink_enabled = *item.bool_value; app_ctx.saveConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "mc_mqtt_enabled") == 0) + case settings::ui::SettingId::McMqttEnabled: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_mqtt_enabled = *item.bool_value; app_ctx.saveConfig(); build_item_list(); + break; } - if (item.pref_key && strcmp(item.pref_key, "mc_mqtt_uplink") == 0) + case settings::ui::SettingId::McMqttUplink: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_mqtt_uplink_enabled = *item.bool_value; app_ctx.saveConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "mc_mqtt_downlink") == 0) + case settings::ui::SettingId::McMqttDownlink: { app::IAppFacade& app_ctx = app::appFacade(); app_ctx.getConfig().meshcore_config.meshcore_mqtt_downlink_enabled = *item.bool_value; app_ctx.saveConfig(); + break; } - if (item.pref_key && strcmp(item.pref_key, "ble_enabled") == 0) - { - app::IAppFacade& app_ctx = app::appFacade(); - app_ctx.getConfig().ble_enabled = *item.bool_value; - app_ctx.saveConfig(); - app_ctx.setBleEnabled(*item.bool_value); - } - if (item.pref_key && strcmp(item.pref_key, "wifi_enabled") == 0) + case settings::ui::SettingId::WifiEnabled: { wifi_runtime::Config config{}; config.enabled = *item.bool_value; @@ -5389,17 +5221,25 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) } refresh_wifi_state_from_runtime(); build_item_list(); + break; } - if (item.pref_key && strcmp(item.pref_key, "vibration_enabled") == 0 && *item.bool_value) + case settings::ui::SettingId::VibrationEnabled: { - device_runtime::trigger_haptic(); + if (*item.bool_value) + { + device_runtime::trigger_haptic(); + } + break; + } + default: + break; } } return true; } if (item.type == settings::ui::SettingType::Enum) { - if (item.pref_key && strcmp(item.pref_key, "display_locale") == 0) + if (id == settings::ui::SettingId::DisplayLocale) { refresh_language_pack_options(); update_item_value(widget); @@ -5414,46 +5254,56 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) } if (item.type == settings::ui::SettingType::Action) { - if (item.pref_key && strcmp(item.pref_key, "enabled_imes") == 0) + switch (id) { + case settings::ui::SettingId::MtPrimaryKeyGenerate: + generate_meshtastic_channel_key(true); + break; + case settings::ui::SettingId::MtSecondaryKeyGenerate: + generate_meshtastic_channel_key(false); + break; + case settings::ui::SettingId::McChannelKeyGenerate: + generate_meshcore_channel_key(); + break; + case settings::ui::SettingId::EnabledImes: refresh_language_pack_options(); update_item_value(widget); open_enabled_imes_modal(widget); - } - else if (item.pref_key && strcmp(item.pref_key, "gps_diagnostics") == 0) - { + break; + case settings::ui::SettingId::GpsDiagnostics: open_gps_diagnostics_modal(); - } - else if (item.pref_key && strcmp(item.pref_key, "chat_reset_mesh") == 0) + break; + case settings::ui::SettingId::ChatResetMesh: { ScopedSettingsBusyOverlay busy(::ui::i18n::tr("Resetting mesh..."), ::ui::i18n::tr("Applying defaults"), 15); reset_mesh_settings(); + break; } - else if (item.pref_key && strcmp(item.pref_key, "chat_reset_nodes") == 0) + case settings::ui::SettingId::ChatResetNodes: { ScopedSettingsBusyOverlay busy(::ui::i18n::tr("Resetting nodes..."), ::ui::i18n::tr("Clearing node database"), 35); reset_node_db(); + break; } - else if (item.pref_key && strcmp(item.pref_key, "chat_clear_messages") == 0) + case settings::ui::SettingId::ChatClearMessages: { ScopedSettingsBusyOverlay busy(::ui::i18n::tr("Clearing messages..."), ::ui::i18n::tr("Updating chat storage"), 35); clear_message_db(); + break; } - else if (item.pref_key && strcmp(item.pref_key, "system_factory_reset") == 0) - { + case settings::ui::SettingId::SystemFactoryReset: open_factory_reset_modal(); - } - else if (item.pref_key && strcmp(item.pref_key, "manual_time_set") == 0) - { + break; + case settings::ui::SettingId::ManualTimeSet: open_manual_datetime_modal(widget); - } - else if (item.pref_key && strcmp(item.pref_key, "c6_enter_download") == 0) + break; + case settings::ui::SettingId::C6EnterDownload: { ScopedSettingsBusyOverlay busy(::ui::i18n::tr("Preparing companion..."), ::ui::i18n::tr("Requesting download mode"), @@ -5464,14 +5314,17 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) ::ui::feedback::show_notice( ::ui::i18n::tr(ok ? "C6 download mode requested" : "C6 download mode failed"), ok ? 3000 : 4000); + break; } - else if (item.pref_key && strcmp(item.pref_key, "wifi_scan") == 0) + case settings::ui::SettingId::WifiScan: { ScopedSettingsBusyOverlay busy(::ui::i18n::tr("Scanning Wi-Fi..."), ::ui::i18n::tr("Looking for nearby networks"), 20); - std::vector results; - if (!wifi_runtime::scan(results)) + clear_wifi_scan_options(); + auto& options = dynamic_options(); + size_t result_count = 0; + if (!wifi_runtime::scan(options.wifi_scan_results, kMaxWifiNetworks, result_count)) { refresh_wifi_state_from_runtime(); } @@ -5480,7 +5333,7 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) busy.update(::ui::i18n::tr("Scanning Wi-Fi..."), ::ui::i18n::tr("Updating network list"), 80); - rebuild_wifi_scan_options(results); + rebuild_wifi_scan_options(result_count); for (size_t i = 0; i < kWifiNetworkOptionCount; ++i) { if (std::strcmp(dynamic_options().wifi_scan_results[i].ssid, @@ -5493,8 +5346,9 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) refresh_wifi_state_from_runtime(); } build_item_list(); + break; } - else if (item.pref_key && strcmp(item.pref_key, "wifi_connect") == 0) + case settings::ui::SettingId::WifiConnect: { wifi_runtime::Config config{}; config.enabled = true; @@ -5518,8 +5372,9 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) refresh_wifi_state_from_runtime(); } build_item_list(); + break; } - else if (item.pref_key && strcmp(item.pref_key, "wifi_disconnect") == 0) + case settings::ui::SettingId::WifiDisconnect: { ScopedSettingsBusyOverlay busy(::ui::i18n::tr("Disconnecting Wi-Fi..."), g_settings.wifi_ssid, @@ -5527,8 +5382,9 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) wifi_runtime::disconnect(); refresh_wifi_state_from_runtime(); build_item_list(); + break; } - else if (item.pref_key && strcmp(item.pref_key, "fw_check") == 0) + case settings::ui::SettingId::FwCheck: { if (!firmware_update_runtime::start_check()) { @@ -5553,8 +5409,9 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) { sync_firmware_update_ui(false); } + break; } - else if (item.pref_key && strcmp(item.pref_key, "fw_install") == 0) + case settings::ui::SettingId::FwInstall: { if (!firmware_update_runtime::start_install()) { @@ -5579,8 +5436,9 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) { sync_firmware_update_ui(false); } + break; } - else if (item.pref_key && strcmp(item.pref_key, "settings_backup") == 0) + case settings::ui::SettingId::SettingsBackup: { const settings_backup_runtime::Status before = settings_backup_runtime::status(); if (!before.sd_present) @@ -5610,8 +5468,9 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) } } refresh_visible_item_values(); + break; } - else if (item.pref_key && strcmp(item.pref_key, "settings_restore") == 0) + case settings::ui::SettingId::SettingsRestore: { const settings_backup_runtime::Status status = settings_backup_runtime::status(); if (!status.sd_present) @@ -5628,6 +5487,10 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) } refresh_settings_backup_state_from_runtime(); refresh_visible_item_values(); + break; + } + default: + break; } return true; } @@ -5699,49 +5562,48 @@ static void settings_back_cb(void* /*user_data*/) static void bind_dynamic_option_storage_to_items() { auto& options = dynamic_options(); - for (settings::ui::SettingItem& item : kChatItems) + for (const CategoryDef& category : kCategories) { - if (has_pref_key(item, "chat_region")) + settings::ui::spec::bind_items( + const_cast(category.items), + category.item_count); + for (size_t index = 0; index < category.item_count; ++index) { - item.options = options.chat_region_options; - } - } - for (settings::ui::SettingItem& item : kNetworkItems) - { - if (has_pref_key(item, "net_tx_power") || has_pref_key(item, "mc_tx_power")) - { - item.options = options.tx_power_options; - } - else if (has_pref_key(item, "mc_region_preset")) - { - item.options = options.meshcore_region_preset_options; - } - } - for (settings::ui::SettingItem& item : kScreenItems) - { - if (has_pref_key(item, "display_locale")) - { - item.options = options.locale_options; - } - else if (has_pref_key(item, "timezone_profile")) - { - item.options = options.time_zone_options; - } - } - for (settings::ui::SettingItem& item : kWifiItems) - { - if (has_pref_key(item, "wifi_network")) - { - item.options = options.wifi_network_options; + settings::ui::SettingItem& item = + const_cast(category.items[index]); + switch (::settings::ui::spec::dynamic_option_kind(item.id)) + { + case ::settings::ui::spec::DynamicOptionKind::ChatRegion: + item.options = options.chat_region_options; + break; + case ::settings::ui::spec::DynamicOptionKind::MeshCoreRegionPreset: + item.options = options.meshcore_region_preset_options; + break; + case ::settings::ui::spec::DynamicOptionKind::Locale: + item.options = options.locale_options; + break; + case ::settings::ui::spec::DynamicOptionKind::TimeZone: + item.options = options.time_zone_options; + break; + case ::settings::ui::spec::DynamicOptionKind::WifiNetwork: + item.options = options.wifi_network_options; + break; + case ::settings::ui::spec::DynamicOptionKind::TxPower: + item.options = options.tx_power_options; + break; + case ::settings::ui::spec::DynamicOptionKind::None: + default: + break; + } } } } static void refresh_timezone_option_count() { - for (settings::ui::SettingItem& item : kScreenItems) + for (settings::ui::SettingItem& item : kDeviceItems) { - if (has_pref_key(item, "timezone_profile")) + if (item_id(item) == settings::ui::SettingId::TimezoneProfile) { item.option_count = kTimeZoneOptionCount; return; diff --git a/modules/ui_shared/src/ui/screens/settings/settings_spec.cpp b/modules/ui_shared/src/ui/screens/settings/settings_spec.cpp new file mode 100644 index 00000000..ff88ae3c --- /dev/null +++ b/modules/ui_shared/src/ui/screens/settings/settings_spec.cpp @@ -0,0 +1,694 @@ +#include "ui/screens/settings/settings_spec.h" + +#include "chat/infra/mesh_protocol_utils.h" +#include + +namespace settings::ui::spec +{ + +namespace +{ + +struct SettingSpec +{ + SettingId id; + const char* key; + DynamicOptionKind dynamic_options; +}; + +constexpr SettingSpec kSpecs[] = { + {SettingId::ChatUser, "chat_user", DynamicOptionKind::None}, + {SettingId::ChatShort, "chat_short", DynamicOptionKind::None}, + {SettingId::MeshProtocol, "mesh_protocol", DynamicOptionKind::None}, + {SettingId::ChatMessageAlerts, "chat_message_alerts", DynamicOptionKind::None}, + {SettingId::ChatContactAlerts, "chat_contact_alerts", DynamicOptionKind::None}, + {SettingId::ChatRegion, "chat_region", DynamicOptionKind::ChatRegion}, + {SettingId::ChatChannel, "chat_channel", DynamicOptionKind::None}, + {SettingId::ChatPsk, "chat_psk", DynamicOptionKind::None}, + {SettingId::MtPrimaryEnabled, "mt_primary_enabled", DynamicOptionKind::None}, + {SettingId::MtPrimaryName, "mt_primary_name", DynamicOptionKind::None}, + {SettingId::MtPrimaryKey, "mt_primary_key", DynamicOptionKind::None}, + {SettingId::MtPrimaryKeyGenerate, "mt_primary_key_generate", DynamicOptionKind::None}, + {SettingId::MtPrimaryUplink, "mt_primary_uplink", DynamicOptionKind::None}, + {SettingId::MtPrimaryDownlink, "mt_primary_downlink", DynamicOptionKind::None}, + {SettingId::MtSecondaryEnabled, "mt_secondary_enabled", DynamicOptionKind::None}, + {SettingId::MtSecondaryName, "mt_secondary_name", DynamicOptionKind::None}, + {SettingId::MtSecondaryKey, "mt_secondary_key", DynamicOptionKind::None}, + {SettingId::MtSecondaryKeyGenerate, "mt_secondary_key_generate", DynamicOptionKind::None}, + {SettingId::MtSecondaryUplink, "mt_secondary_uplink", DynamicOptionKind::None}, + {SettingId::MtSecondaryDownlink, "mt_secondary_downlink", DynamicOptionKind::None}, + {SettingId::PrivacyEncrypt, "privacy_encrypt", DynamicOptionKind::None}, + {SettingId::MtMqttEnabled, "mt_mqtt_enabled", DynamicOptionKind::None}, + {SettingId::MtMqttHost, "mt_mqtt_host", DynamicOptionKind::None}, + {SettingId::MtMqttPort, "mt_mqtt_port", DynamicOptionKind::None}, + {SettingId::MtMqttRoot, "mt_mqtt_root", DynamicOptionKind::None}, + {SettingId::MtMqttUser, "mt_mqtt_user", DynamicOptionKind::None}, + {SettingId::MtMqttPass, "mt_mqtt_pass", DynamicOptionKind::None}, + {SettingId::MtMqttUplink, "mt_mqtt_uplink", DynamicOptionKind::None}, + {SettingId::MtMqttDownlink, "mt_mqtt_downlink", DynamicOptionKind::None}, + {SettingId::McMqttEnabled, "mc_mqtt_enabled", DynamicOptionKind::None}, + {SettingId::McMqttHost, "mc_mqtt_host", DynamicOptionKind::None}, + {SettingId::McMqttPort, "mc_mqtt_port", DynamicOptionKind::None}, + {SettingId::McMqttRoot, "mc_mqtt_root", DynamicOptionKind::None}, + {SettingId::McMqttUser, "mc_mqtt_user", DynamicOptionKind::None}, + {SettingId::McMqttPass, "mc_mqtt_pass", DynamicOptionKind::None}, + {SettingId::McMqttUplink, "mc_mqtt_uplink", DynamicOptionKind::None}, + {SettingId::McMqttDownlink, "mc_mqtt_downlink", DynamicOptionKind::None}, + {SettingId::McChannelSlot, "mc_channel_slot", DynamicOptionKind::None}, + {SettingId::McChannelName, "mc_channel_name", DynamicOptionKind::None}, + {SettingId::McChannelKey, "mc_channel_key", DynamicOptionKind::None}, + {SettingId::McChannelKeyGenerate, "mc_channel_key_generate", DynamicOptionKind::None}, + {SettingId::RtBearer, "rt_bearer", DynamicOptionKind::None}, + {SettingId::RtLoraEnabled, "rt_lora_enabled", DynamicOptionKind::None}, + {SettingId::RtDisplayName, "rt_display_name", DynamicOptionKind::None}, + {SettingId::RtIdentityHash, "rt_identity_hash", DynamicOptionKind::None}, + {SettingId::RtLxmfAddress, "rt_lxmf_address", DynamicOptionKind::None}, + {SettingId::RtWifiGateway, "rt_wifi_gateway", DynamicOptionKind::None}, + {SettingId::RtWifiHost, "rt_wifi_host", DynamicOptionKind::None}, + {SettingId::RtWifiPort, "rt_wifi_port", DynamicOptionKind::None}, + {SettingId::RtWifiAuto, "rt_wifi_auto", DynamicOptionKind::None}, + {SettingId::RtAnonymousPeer, "rt_anonymous_peer", DynamicOptionKind::None}, + {SettingId::NetUsePreset, "net_use_preset", DynamicOptionKind::None}, + {SettingId::NetPreset, "net_preset", DynamicOptionKind::None}, + {SettingId::NetBw, "net_bw", DynamicOptionKind::None}, + {SettingId::NetSf, "net_sf", DynamicOptionKind::None}, + {SettingId::NetCr, "net_cr", DynamicOptionKind::None}, + {SettingId::NetTxPower, "net_tx_power", DynamicOptionKind::TxPower}, + {SettingId::NetHopLimit, "net_hop_limit", DynamicOptionKind::None}, + {SettingId::NetTxEnabled, "net_tx_enabled", DynamicOptionKind::None}, + {SettingId::NetOverrideDuty, "net_override_duty", DynamicOptionKind::None}, + {SettingId::NetChannelNum, "net_channel_num", DynamicOptionKind::None}, + {SettingId::NetFreqOffset, "net_freq_offset", DynamicOptionKind::None}, + {SettingId::NetOverrideFreq, "net_override_freq", DynamicOptionKind::None}, + {SettingId::NetRelay, "net_relay", DynamicOptionKind::None}, + {SettingId::NetDutyCycle, "net_duty_cycle", DynamicOptionKind::None}, + {SettingId::NetUtil, "net_util", DynamicOptionKind::None}, + {SettingId::McRegionPreset, "mc_region_preset", DynamicOptionKind::MeshCoreRegionPreset}, + {SettingId::McFreq, "mc_freq", DynamicOptionKind::None}, + {SettingId::McBw, "mc_bw", DynamicOptionKind::None}, + {SettingId::McSf, "mc_sf", DynamicOptionKind::None}, + {SettingId::McCr, "mc_cr", DynamicOptionKind::None}, + {SettingId::McTxPower, "mc_tx_power", DynamicOptionKind::TxPower}, + {SettingId::McRepeat, "mc_repeat", DynamicOptionKind::None}, + {SettingId::McRxDelay, "mc_rx_delay", DynamicOptionKind::None}, + {SettingId::McAirtime, "mc_airtime", DynamicOptionKind::None}, + {SettingId::McFloodMax, "mc_flood_max", DynamicOptionKind::None}, + {SettingId::McMultiAcks, "mc_multi_acks", DynamicOptionKind::None}, + {SettingId::McSendProfile, "mc_send_prof", DynamicOptionKind::None}, + {SettingId::McForwardProfile, "mc_fwd_prof", DynamicOptionKind::None}, + {SettingId::GpsEnabled, "gps_enabled", DynamicOptionKind::None}, + {SettingId::GpsInitBaud, "gps_init_baud", DynamicOptionKind::None}, + {SettingId::GpsInitProbeMs, "gps_init_probe_ms", DynamicOptionKind::None}, + {SettingId::GpsInitProfile, "gps_init_profile", DynamicOptionKind::None}, + {SettingId::GpsInitRxm, "gps_init_rxm", DynamicOptionKind::None}, + {SettingId::GpsInitGnss, "gps_init_gnss", DynamicOptionKind::None}, + {SettingId::GpsInitNmea, "gps_init_nmea", DynamicOptionKind::None}, + {SettingId::GpsMode, "gps_mode", DynamicOptionKind::None}, + {SettingId::GpsSatMask, "gps_sat_mask", DynamicOptionKind::None}, + {SettingId::GpsStrategy, "gps_strategy", DynamicOptionKind::None}, + {SettingId::GpsInterval, "gps_interval", DynamicOptionKind::None}, + {SettingId::GpsAltRef, "gps_alt_ref", DynamicOptionKind::None}, + {SettingId::GpsCoordFmt, "gps_coord_fmt", DynamicOptionKind::None}, + {SettingId::ExternalNmea, "external_nmea", DynamicOptionKind::None}, + {SettingId::ExternalNmeaSent, "external_nmea_sent", DynamicOptionKind::None}, + {SettingId::GpsDiagnostics, "gps_diagnostics", DynamicOptionKind::None}, + {SettingId::MapCoord, "map_coord", DynamicOptionKind::None}, + {SettingId::MapSource, "map_source", DynamicOptionKind::None}, + {SettingId::MapContour, "map_contour", DynamicOptionKind::None}, + {SettingId::MapTrack, "map_track", DynamicOptionKind::None}, + {SettingId::MapTrackInterval, "map_track_interval", DynamicOptionKind::None}, + {SettingId::MapTrackFormat, "map_track_format", DynamicOptionKind::None}, + {SettingId::DisplayLocale, "display_locale", DynamicOptionKind::Locale}, + {SettingId::EnabledImes, "enabled_imes", DynamicOptionKind::None}, + {SettingId::ScreenTimeout, "screen_timeout", DynamicOptionKind::None}, + {SettingId::ScreenBrightness, "screen_brightness", DynamicOptionKind::None}, + {SettingId::SpeakerVolume, "speaker_volume", DynamicOptionKind::None}, + {SettingId::VibrationEnabled, "vibration_enabled", DynamicOptionKind::None}, + {SettingId::C6CompanionStatus, "c6_companion_status", DynamicOptionKind::None}, + {SettingId::C6EnterDownload, "c6_enter_download", DynamicOptionKind::None}, + {SettingId::TimezoneProfile, "timezone_profile", DynamicOptionKind::TimeZone}, + {SettingId::ManualTimeSet, "manual_time_set", DynamicOptionKind::None}, + {SettingId::GaugeDesignMah, "gauge_design_mah", DynamicOptionKind::None}, + {SettingId::GaugeFullMah, "gauge_full_mah", DynamicOptionKind::None}, + {SettingId::WifiEnabled, "wifi_enabled", DynamicOptionKind::None}, + {SettingId::WifiStatus, "wifi_status", DynamicOptionKind::None}, + {SettingId::WifiScan, "wifi_scan", DynamicOptionKind::None}, + {SettingId::WifiNetwork, "wifi_network", DynamicOptionKind::WifiNetwork}, + {SettingId::WifiSsid, "wifi_ssid", DynamicOptionKind::None}, + {SettingId::WifiPassword, "wifi_password", DynamicOptionKind::None}, + {SettingId::WifiConnect, "wifi_connect", DynamicOptionKind::None}, + {SettingId::WifiDisconnect, "wifi_disconnect", DynamicOptionKind::None}, + {SettingId::FwCurrent, "fw_current", DynamicOptionKind::None}, + {SettingId::FwLatest, "fw_latest", DynamicOptionKind::None}, + {SettingId::FwStatus, "fw_status", DynamicOptionKind::None}, + {SettingId::FwCheck, "fw_check", DynamicOptionKind::None}, + {SettingId::FwInstall, "fw_install", DynamicOptionKind::None}, + {SettingId::SettingsBackupStatus, "settings_backup_status", DynamicOptionKind::None}, + {SettingId::SettingsBackup, "settings_backup", DynamicOptionKind::None}, + {SettingId::SettingsRestore, "settings_restore", DynamicOptionKind::None}, + {SettingId::AdvDebug, "adv_debug", DynamicOptionKind::None}, + {SettingId::ChatResetMesh, "chat_reset_mesh", DynamicOptionKind::None}, + {SettingId::ChatResetNodes, "chat_reset_nodes", DynamicOptionKind::None}, + {SettingId::ChatClearMessages, "chat_clear_messages", DynamicOptionKind::None}, + {SettingId::SystemFactoryReset, "system_factory_reset", DynamicOptionKind::None}, +}; + +const SettingSpec* find_spec(SettingId id) +{ + for (const SettingSpec& spec : kSpecs) + { + if (spec.id == id) + { + return &spec; + } + } + return nullptr; +} + +bool is_meshtastic_protocol(const VisibilityContext& context) +{ + return context.protocol == chat::MeshProtocol::Meshtastic; +} + +bool is_meshcore_protocol(const VisibilityContext& context) +{ + return context.protocol == chat::MeshProtocol::MeshCore; +} + +bool is_reticulum_protocol(const VisibilityContext& context) +{ + return chat::infra::isReticulumMeshProtocol(context.protocol); +} + +bool is_meshtastic_mqtt(SettingId id) +{ + switch (id) + { + case SettingId::MtMqttEnabled: + case SettingId::MtMqttHost: + case SettingId::MtMqttPort: + case SettingId::MtMqttRoot: + case SettingId::MtMqttUser: + case SettingId::MtMqttPass: + case SettingId::MtMqttUplink: + case SettingId::MtMqttDownlink: + return true; + default: + return false; + } +} + +bool is_meshcore_mqtt(SettingId id) +{ + switch (id) + { + case SettingId::McMqttEnabled: + case SettingId::McMqttHost: + case SettingId::McMqttPort: + case SettingId::McMqttRoot: + case SettingId::McMqttUser: + case SettingId::McMqttPass: + case SettingId::McMqttUplink: + case SettingId::McMqttDownlink: + return true; + default: + return false; + } +} + +bool is_meshtastic_channel(SettingId id) +{ + switch (id) + { + case SettingId::ChatRegion: + case SettingId::ChatChannel: + case SettingId::ChatPsk: + case SettingId::MtPrimaryEnabled: + case SettingId::MtPrimaryName: + case SettingId::MtPrimaryKey: + case SettingId::MtPrimaryKeyGenerate: + case SettingId::MtPrimaryUplink: + case SettingId::MtPrimaryDownlink: + case SettingId::MtSecondaryEnabled: + case SettingId::MtSecondaryName: + case SettingId::MtSecondaryKey: + case SettingId::MtSecondaryKeyGenerate: + case SettingId::MtSecondaryUplink: + case SettingId::MtSecondaryDownlink: + case SettingId::PrivacyEncrypt: + return true; + default: + return false; + } +} + +bool is_meshcore_channel(SettingId id) +{ + switch (id) + { + case SettingId::McChannelSlot: + case SettingId::McChannelName: + case SettingId::McChannelKey: + case SettingId::McChannelKeyGenerate: + return true; + default: + return false; + } +} + +bool is_reticulum_mesh(SettingId id) +{ + switch (id) + { + case SettingId::RtBearer: + case SettingId::RtLoraEnabled: + case SettingId::RtDisplayName: + case SettingId::RtIdentityHash: + case SettingId::RtLxmfAddress: + case SettingId::RtWifiGateway: + case SettingId::RtWifiHost: + case SettingId::RtWifiPort: + case SettingId::RtWifiAuto: + case SettingId::RtAnonymousPeer: + return true; + default: + return false; + } +} + +bool is_meshcore_radio(SettingId id) +{ + switch (id) + { + case SettingId::McRegionPreset: + case SettingId::McFreq: + case SettingId::McBw: + case SettingId::McSf: + case SettingId::McCr: + case SettingId::McTxPower: + case SettingId::McRepeat: + case SettingId::McRxDelay: + case SettingId::McAirtime: + case SettingId::McFloodMax: + case SettingId::McMultiAcks: + case SettingId::McSendProfile: + case SettingId::McForwardProfile: + return true; + default: + return false; + } +} + +bool is_lora_radio(SettingId id) +{ + switch (id) + { + case SettingId::NetUsePreset: + case SettingId::NetPreset: + case SettingId::NetBw: + case SettingId::NetSf: + case SettingId::NetCr: + case SettingId::NetTxPower: + case SettingId::NetHopLimit: + case SettingId::NetTxEnabled: + case SettingId::NetOverrideDuty: + case SettingId::NetChannelNum: + case SettingId::NetFreqOffset: + case SettingId::NetOverrideFreq: + case SettingId::NetDutyCycle: + case SettingId::NetUtil: + return true; + default: + return false; + } +} + +bool is_mt_secondary_child(SettingId id) +{ + switch (id) + { + case SettingId::MtSecondaryName: + case SettingId::MtSecondaryKey: + case SettingId::MtSecondaryKeyGenerate: + case SettingId::MtSecondaryUplink: + case SettingId::MtSecondaryDownlink: + return true; + default: + return false; + } +} + +bool is_reticulum_wifi_detail(SettingId id) +{ + switch (id) + { + case SettingId::RtWifiHost: + case SettingId::RtWifiPort: + case SettingId::RtWifiAuto: + return true; + default: + return false; + } +} + +bool is_wifi_setting(SettingId id) +{ + switch (id) + { + case SettingId::WifiEnabled: + case SettingId::WifiScan: + case SettingId::WifiConnect: + case SettingId::WifiDisconnect: + case SettingId::WifiSsid: + case SettingId::WifiPassword: + return true; + default: + return false; + } +} + +bool is_firmware_update_setting(SettingId id) +{ + switch (id) + { + case SettingId::FwCurrent: + case SettingId::FwLatest: + case SettingId::FwStatus: + case SettingId::FwCheck: + case SettingId::FwInstall: + return true; + default: + return false; + } +} + +bool is_settings_backup_setting(SettingId id) +{ + switch (id) + { + case SettingId::SettingsBackupStatus: + case SettingId::SettingsBackup: + case SettingId::SettingsRestore: + return true; + default: + return false; + } +} + +bool is_wireless_companion_setting(SettingId id) +{ + switch (id) + { + case SettingId::C6CompanionStatus: + case SettingId::C6EnterDownload: + return true; + default: + return false; + } +} + +bool is_gps_init_policy_setting(SettingId id) +{ + switch (id) + { + case SettingId::GpsInitProbeMs: + case SettingId::GpsInitProfile: + case SettingId::GpsInitRxm: + case SettingId::GpsInitGnss: + case SettingId::GpsInitNmea: + return true; + default: + return false; + } +} + +bool is_gps_gnss_setting(SettingId id) +{ + switch (id) + { + case SettingId::GpsMode: + case SettingId::GpsSatMask: + case SettingId::GpsStrategy: + return true; + default: + return false; + } +} + +bool is_external_nmea_setting(SettingId id) +{ + return id == SettingId::ExternalNmea || id == SettingId::ExternalNmeaSent; +} + +bool is_gauge_setting(SettingId id) +{ + return id == SettingId::GaugeDesignMah || id == SettingId::GaugeFullMah; +} + +} // namespace + +SettingId id_for_key(const char* pref_key) +{ + if (!pref_key) + { + return SettingId::Unknown; + } + for (const SettingSpec& spec : kSpecs) + { + if (std::strcmp(spec.key, pref_key) == 0) + { + return spec.id; + } + } + return SettingId::Unknown; +} + +const char* key_for_id(SettingId id) +{ + const SettingSpec* spec = find_spec(id); + return spec ? spec->key : nullptr; +} + +void bind_item(SettingItem& item) +{ + if (item.id == SettingId::Unknown) + { + item.id = id_for_key(item.pref_key); + } +} + +void bind_items(SettingItem* items, std::size_t count) +{ + if (!items) + { + return; + } + for (std::size_t index = 0; index < count; ++index) + { + bind_item(items[index]); + } +} + +DynamicOptionKind dynamic_option_kind(SettingId id) +{ + const SettingSpec* spec = find_spec(id); + return spec ? spec->dynamic_options : DynamicOptionKind::None; +} + +bool option_labels_are_translated(SettingId id) +{ + return id != SettingId::DisplayLocale && id != SettingId::WifiNetwork; +} + +bool option_labels_use_content_font(SettingId id) +{ + return id == SettingId::DisplayLocale || id == SettingId::WifiNetwork; +} + +bool is_settings_store_owned_enum(SettingId id) +{ + switch (id) + { + case SettingId::ScreenBrightness: + case SettingId::SpeakerVolume: + case SettingId::ChatMessageAlerts: + case SettingId::ChatContactAlerts: + return true; + default: + return false; + } +} + +bool is_settings_store_owned_toggle(SettingId id) +{ + return id == SettingId::VibrationEnabled || id == SettingId::AdvDebug; +} + +bool should_show(SettingId id, const VisibilityContext& context) +{ + if (id == SettingId::Unknown) + { + return true; + } + if (id == SettingId::WifiNetwork) + { + return context.wifi_supported && context.has_wifi_networks; + } + if (is_wifi_setting(id) && !context.wifi_supported) + { + return false; + } + if (is_firmware_update_setting(id) && !context.firmware_update_supported) + { + return false; + } + if (is_settings_backup_setting(id) && !context.settings_backup_supported) + { + return false; + } + if (is_wireless_companion_setting(id) && !context.wireless_companion_supported) + { + return false; + } + + const bool meshtastic = is_meshtastic_protocol(context); + const bool meshcore = is_meshcore_protocol(context); + const bool reticulum = is_reticulum_protocol(context); + + if (is_meshtastic_mqtt(id) && !meshtastic) + { + return false; + } + if (is_meshcore_mqtt(id) && !meshcore) + { + return false; + } + if (is_meshtastic_channel(id) && !meshtastic) + { + return false; + } + if (is_meshcore_channel(id) && !meshcore) + { + return false; + } + if (is_reticulum_mesh(id) && !reticulum) + { + return false; + } + if (is_meshcore_radio(id) && !meshcore) + { + return false; + } + if (is_lora_radio(id)) + { + if (meshcore) + { + return false; + } + if (reticulum && !context.reticulum_lora_visible) + { + return false; + } + } + if (is_mt_secondary_child(id) && !context.mt_secondary_enabled) + { + return false; + } + if (is_reticulum_wifi_detail(id) && reticulum && !context.reticulum_wifi_visible) + { + return false; + } + if (id == SettingId::NetRelay) + { + return false; + } + + if (id == SettingId::ScreenBrightness && !context.screen_brightness_supported) + { + return false; + } + if (id == SettingId::ScreenTimeout && !context.screen_timeout_supported) + { + return false; + } + if (id == SettingId::GpsInitBaud && !context.gps_baud_supported) + { + return false; + } + if (is_gps_init_policy_setting(id) && !context.gps_init_policy_supported) + { + return false; + } + if (is_gps_gnss_setting(id) && !context.gps_gnss_supported) + { + return false; + } + if (id == SettingId::GpsInterval && !context.gps_interval_supported) + { + return false; + } + if (id == SettingId::GpsAltRef && !context.gps_alt_ref_supported) + { + return false; + } + if (id == SettingId::GpsCoordFmt && !context.gps_coord_format_supported) + { + return false; + } + if (is_external_nmea_setting(id) && !context.external_nmea_supported) + { + return false; + } + if (is_gauge_setting(id) && !context.battery_gauge_supported) + { + return false; + } + + if (meshcore) + { + switch (id) + { + case SettingId::NetOverrideDuty: + case SettingId::NetChannelNum: + case SettingId::NetFreqOffset: + case SettingId::NetOverrideFreq: + return false; + default: + break; + } + } + else if (reticulum) + { + switch (id) + { + case SettingId::NetUsePreset: + case SettingId::NetPreset: + case SettingId::NetHopLimit: + case SettingId::NetOverrideDuty: + case SettingId::NetChannelNum: + case SettingId::NetFreqOffset: + case SettingId::NetDutyCycle: + case SettingId::NetUtil: + case SettingId::RtLoraEnabled: + case SettingId::RtWifiGateway: + return false; + default: + break; + } + } + else + { + if (id == SettingId::NetPreset) + { + return context.mt_use_preset; + } + if (id == SettingId::NetBw || id == SettingId::NetSf || id == SettingId::NetCr) + { + return !context.mt_use_preset; + } + } + + return true; +} + +} // namespace settings::ui::spec diff --git a/modules/ui_shared/src/ui/ui_status.cpp b/modules/ui_shared/src/ui/ui_status.cpp index b3d17a08..b02bdce1 100644 --- a/modules/ui_shared/src/ui/ui_status.cpp +++ b/modules/ui_shared/src/ui/ui_status.cpp @@ -23,7 +23,11 @@ #include -#if __has_include("ble/ble_manager.h") +#ifndef TRAIL_MATE_ENABLE_BLE +#define TRAIL_MATE_ENABLE_BLE 0 +#endif + +#if TRAIL_MATE_ENABLE_BLE && __has_include("ble/ble_manager.h") #include "ble/ble_manager.h" #define TRAIL_MATE_UI_STATUS_HAS_BLE_MANAGER_HEADER 1 #else @@ -40,7 +44,9 @@ extern "C" extern const lv_image_dsc_t team_topbar; #endif extern const lv_image_dsc_t tracker_topbar; +#if TRAIL_MATE_ENABLE_BLE extern const lv_image_dsc_t ble_topbar; +#endif extern const lv_image_dsc_t lora_mod_topbar; extern const lv_image_dsc_t fsk_mod_topbar; extern const lv_image_dsc_t walkie_monitor_topbar; @@ -60,7 +66,9 @@ struct StatusSnapshot bool gps_enabled = false; bool wifi_enabled = false; bool team_active = false; +#if TRAIL_MATE_ENABLE_BLE bool ble_enabled = false; +#endif bool radio_mod_visible = false; bool radio_mod_fsk = false; bool walkie_monitor = false; @@ -144,7 +152,7 @@ StatusSnapshot collect_status() { const auto& cfg = app::configFacade().getConfig(); snap.route_active = cfg.route_enabled && (cfg.route_path[0] != '\0'); -#if !defined(TRAIL_MATE_FINAL_IDF_NO_APP_FACADE) +#if TRAIL_MATE_ENABLE_BLE && !defined(TRAIL_MATE_FINAL_IDF_NO_APP_FACADE) snap.ble_enabled = app::runtimeFacade().isBleEnabled(); #if TRAIL_MATE_UI_STATUS_HAS_BLE_MANAGER_HEADER if (auto* ble = app::runtimeFacade().getBleManager()) @@ -210,14 +218,21 @@ void apply_menu_icons(const StatusSnapshot& snap) apply_icon(s_menu_team_icon, nullptr, false); #endif apply_icon(s_menu_msg_icon, &message_topbar, snap.unread > 0); +#if TRAIL_MATE_ENABLE_BLE apply_icon(s_menu_ble_icon, &ble_topbar, snap.ble_enabled); +#else + apply_icon(s_menu_ble_icon, nullptr, false); +#endif apply_icon(s_menu_radio_mod_icon, snap.radio_mod_fsk ? &fsk_mod_topbar : &lora_mod_topbar, snap.radio_mod_visible); apply_icon(s_menu_walkie_monitor_icon, &walkie_monitor_topbar, snap.walkie_monitor); const bool any = snap.route_active || snap.track_recording || snap.gps_enabled || - snap.wifi_enabled || snap.team_active || snap.ble_enabled || + snap.wifi_enabled || snap.team_active || +#if TRAIL_MATE_ENABLE_BLE + snap.ble_enabled || +#endif snap.radio_mod_visible || snap.walkie_monitor || (snap.unread > 0); if (any) { diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/app_tasks.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/app_tasks.h index 7aad0212..55e29103 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/app_tasks.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/app_tasks.h @@ -21,7 +21,7 @@ namespace app class AppTasks { public: - // Keep always-on radio queues shallow so BLE/NimBLE retains internal RAM. + // Keep always-on radio queues shallow so network stacks retain internal RAM. static constexpr size_t RADIO_QUEUE_SIZE = 6; static constexpr size_t MESH_QUEUE_SIZE = 6; diff --git a/platform/esp/arduino_common/library.json b/platform/esp/arduino_common/library.json index 0963f7ee..2ae180a9 100644 --- a/platform/esp/arduino_common/library.json +++ b/platform/esp/arduino_common/library.json @@ -9,6 +9,7 @@ "srcDir": "src", "srcFilter": [ "+<*>", + "-", "-", "-", "-", diff --git a/platform/esp/arduino_common/src/app_config_store.cpp b/platform/esp/arduino_common/src/app_config_store.cpp index 9fd6bda6..e8d68b06 100644 --- a/platform/esp/arduino_common/src/app_config_store.cpp +++ b/platform/esp/arduino_common/src/app_config_store.cpp @@ -1297,7 +1297,8 @@ bool loadAppConfigFromPreferences(AppConfig& config, map_track_enabled = get_bool("map_track", map_track_enabled); map_track_interval = get_uchar(kSettingsKeyMapTrackInterval, map_track_interval); map_track_format = get_uchar(kSettingsKeyMapTrackFormat, map_track_format); - ble_enabled = get_bool("ble_enabled", ble_enabled); + (void)get_bool("ble_enabled", false); + ble_enabled = false; chat_channel = get_uchar("chat_channel", chat_channel); net_duty_cycle = get_bool("net_duty_cycle", net_duty_cycle); net_channel_util = get_uchar("net_util", net_channel_util); @@ -1418,7 +1419,6 @@ bool saveAppConfigToPreferences(const AppConfig& config, const auto& map_track_enabled = config.map_track_enabled; const auto& map_track_interval = config.map_track_interval; const auto& map_track_format = config.map_track_format; - const auto& ble_enabled = config.ble_enabled; const auto& chat_channel = config.chat_channel; const auto& net_duty_cycle = config.net_duty_cycle; const auto& net_channel_util = config.net_channel_util; @@ -1690,7 +1690,7 @@ bool saveAppConfigToPreferences(const AppConfig& config, put_bool("map_track", map_track_enabled); put_uchar(kSettingsKeyMapTrackInterval, map_track_interval); put_uchar(kSettingsKeyMapTrackFormat, map_track_format); - put_bool("ble_enabled", ble_enabled); + put_bool("ble_enabled", false); put_uchar("chat_channel", chat_channel); put_bool("net_duty_cycle", net_duty_cycle); put_uchar("net_util", net_channel_util); diff --git a/platform/esp/arduino_common/src/app_context.cpp b/platform/esp/arduino_common/src/app_context.cpp index 16a2bbdc..cf934a94 100644 --- a/platform/esp/arduino_common/src/app_context.cpp +++ b/platform/esp/arduino_common/src/app_context.cpp @@ -479,7 +479,11 @@ void AppContext::applyPrivacyConfig() bool AppContext::isBleEnabled() const { +#if defined(TRAIL_MATE_ENABLE_BLE) && TRAIL_MATE_ENABLE_BLE return config_.ble_enabled; +#else + return false; +#endif } bool AppContext::init(BoardBase& board, LoraBoard* lora_board, GpsBoard* gps_board, MotionBoard* motion_board, @@ -673,11 +677,16 @@ void AppContext::attachBleManager(std::unique_ptr ble_manager) void AppContext::setBleEnabled(bool enabled) { +#if defined(TRAIL_MATE_ENABLE_BLE) && TRAIL_MATE_ENABLE_BLE config_.ble_enabled = enabled; if (ble_manager_) { ble_manager_->setEnabled(enabled); } +#else + (void)enabled; + config_.ble_enabled = false; +#endif saveConfig(); } diff --git a/platform/esp/arduino_common/src/app_runtime_bootstrap_support.cpp b/platform/esp/arduino_common/src/app_runtime_bootstrap_support.cpp index 73eaf109..0b4c8ca5 100644 --- a/platform/esp/arduino_common/src/app_runtime_bootstrap_support.cpp +++ b/platform/esp/arduino_common/src/app_runtime_bootstrap_support.cpp @@ -34,9 +34,9 @@ bool bootstrapAppContext(app::AppContext& app_context, memory_diag::logHeapSnapshot("bootstrap.after_app_context_init"); app::bindAppFacade(app_context); - ui::boot::set_log_line("Starting BLE..."); + ui::boot::set_log_line("Preparing mesh services..."); app_context.attachBleManager(platform::esp::arduino_common::createBleManager(app_context)); - memory_diag::logHeapSnapshot("bootstrap.after_ble_manager"); + memory_diag::logHeapSnapshot("bootstrap.after_mesh_services"); app_context.attachEventRuntimeHooks(platform::esp::arduino_common::makeAppEventRuntimeHooks()); memory_diag::logHeapSnapshot("bootstrap.after_event_hooks"); result.app_context_bound = true; diff --git a/platform/esp/arduino_common/src/app_runtime_support.cpp b/platform/esp/arduino_common/src/app_runtime_support.cpp index 0a723b97..4296abcc 100644 --- a/platform/esp/arduino_common/src/app_runtime_support.cpp +++ b/platform/esp/arduino_common/src/app_runtime_support.cpp @@ -31,6 +31,10 @@ #define APP_EVENT_LOG_ENABLE 0 #endif +#ifndef TRAIL_MATE_ENABLE_BLE +#define TRAIL_MATE_ENABLE_BLE 0 +#endif + #if APP_EVENT_LOG_ENABLE #define APP_EVENT_LOG(...) std::printf(__VA_ARGS__) #else @@ -200,11 +204,14 @@ void tickRuntime(app::IAppFacade& app_context) } mesh_mqtt::update(app_context); - ble::BleManager* ble_manager = app_context.getBleManager(); - if (ble_manager) +#if TRAIL_MATE_ENABLE_BLE + if (ble::BleManager* ble_manager = app_context.getBleManager()) { ble_manager->update(); } +#else + (void)app_context; +#endif } void updateCoreServices(app::IAppFacade& app_context) @@ -357,6 +364,7 @@ bool dispatchEvent(app::IAppFacade& app_context, sys::Event* event) std::unique_ptr createBleManager(app::IAppBleFacade& app_facade) { +#if TRAIL_MATE_ENABLE_BLE std::unique_ptr ble_manager(new ble::BleManager(app_facade)); if (app_facade.getConfig().ble_enabled && !mesh_mqtt::wantsStandaloneMode(app_facade)) @@ -368,6 +376,10 @@ std::unique_ptr createBleManager(app::IAppBleFacade& app_facade std::printf("[MQTT] BLE startup skipped for standalone mesh MQTT mode\n"); } return ble_manager; +#else + (void)app_facade; + return std::unique_ptr(); +#endif } } // namespace platform::esp::arduino_common diff --git a/platform/esp/arduino_common/src/ble_disabled/arduino_bt_disabled_stubs.cpp b/platform/esp/arduino_common/src/ble_disabled/arduino_bt_disabled_stubs.cpp new file mode 100644 index 00000000..48401448 --- /dev/null +++ b/platform/esp/arduino_common/src/ble_disabled/arduino_bt_disabled_stubs.cpp @@ -0,0 +1,19 @@ +#include "esp_bt.h" +#include "esp_err.h" + +#ifndef TRAIL_MATE_ENABLE_BLE +#define TRAIL_MATE_ENABLE_BLE 0 +#endif + +#if !TRAIL_MATE_ENABLE_BLE +extern "C" bool btInUse() +{ + return true; +} + +extern "C" esp_err_t esp_bt_controller_mem_release(esp_bt_mode_t mode) +{ + (void)mode; + return ESP_OK; +} +#endif diff --git a/platform/esp/arduino_common/src/ble_disabled/ble_manager_disabled.cpp b/platform/esp/arduino_common/src/ble_disabled/ble_manager_disabled.cpp new file mode 100644 index 00000000..8de8def0 --- /dev/null +++ b/platform/esp/arduino_common/src/ble_disabled/ble_manager_disabled.cpp @@ -0,0 +1,56 @@ +#include "ble/ble_manager.h" + +namespace ble +{ + +BleManager::BleManager(app::IAppBleFacade& ctx) + : ctx_(ctx), + active_protocol_(ctx.getMeshProtocol()) +{ +} + +BleManager::~BleManager() = default; + +void BleManager::begin() +{ +} + +void BleManager::setEnabled(bool enabled) +{ + (void)enabled; +} + +void BleManager::update() +{ +} + +void BleManager::applyProtocol(chat::MeshProtocol protocol) +{ + active_protocol_ = protocol; +} + +bool BleManager::getPairingStatus(BlePairingStatus* out) const +{ + if (out) + { + *out = BlePairingStatus{}; + } + return false; +} + +void BleManager::restartService(chat::MeshProtocol protocol) +{ + active_protocol_ = protocol; +} + +void BleManager::shutdownNimble() +{ +} + +std::string BleManager::buildDeviceName(chat::MeshProtocol protocol) const +{ + (void)protocol; + return {}; +} + +} // namespace ble diff --git a/platform/esp/arduino_common/src/chat/infra/mesh_mqtt_client_runtime.cpp b/platform/esp/arduino_common/src/chat/infra/mesh_mqtt_client_runtime.cpp index fbae0fd1..cc01eb04 100644 --- a/platform/esp/arduino_common/src/chat/infra/mesh_mqtt_client_runtime.cpp +++ b/platform/esp/arduino_common/src/chat/infra/mesh_mqtt_client_runtime.cpp @@ -2,7 +2,6 @@ #include "app/app_config.h" #include "app/app_facades.h" -#include "ble/ble_manager.h" #include "chat/infra/meshtastic/mt_radio_config.h" #include "meshtastic/mesh.pb.h" #include "meshtastic/mqtt.pb.h" @@ -23,6 +22,14 @@ #include #include +#ifndef TRAIL_MATE_ENABLE_BLE +#define TRAIL_MATE_ENABLE_BLE 0 +#endif + +#if TRAIL_MATE_ENABLE_BLE +#include "ble/ble_manager.h" +#endif + #if __has_include() #include #define TRAIL_MATE_MESH_MQTT_HAS_WIFI_CLIENT 1 @@ -572,6 +579,7 @@ class PlainMqttRuntime void forceBleOff(app::IAppFacade& app_context) { +#if TRAIL_MATE_ENABLE_BLE if (app_context.isBleEnabled()) { std::printf("[%s][MQTT] disabling BLE for standalone MQTT mode\n", @@ -588,6 +596,9 @@ class PlainMqttRuntime ble_manager->setEnabled(false); } } +#else + (void)app_context; +#endif } void syncAdapterDisabled(app::IAppFacade& app_context) diff --git a/platform/esp/arduino_common/src/platform_ui_firmware_update_runtime.cpp b/platform/esp/arduino_common/src/platform_ui_firmware_update_runtime.cpp index da71ac29..ef2b274d 100644 --- a/platform/esp/arduino_common/src/platform_ui_firmware_update_runtime.cpp +++ b/platform/esp/arduino_common/src/platform_ui_firmware_update_runtime.cpp @@ -9,7 +9,6 @@ #include #include "app/app_facade_access.h" -#include "ble/ble_manager.h" #include "cJSON.h" #include "esp_err.h" #include "esp_ota_ops.h" @@ -20,6 +19,14 @@ #include "platform/ui/http_client_runtime.h" #include "platform/ui/wifi_runtime.h" +#ifndef TRAIL_MATE_ENABLE_BLE +#define TRAIL_MATE_ENABLE_BLE 0 +#endif + +#if TRAIL_MATE_ENABLE_BLE +#include "ble/ble_manager.h" +#endif + #ifdef INADDR_NONE #undef INADDR_NONE #endif @@ -710,6 +717,7 @@ bool battery_allows_install(std::string& out_error) void restore_ble_after_failure(bool restore_ble) { +#if TRAIL_MATE_ENABLE_BLE if (!restore_ble || !app::hasAppFacade()) { return; @@ -718,6 +726,9 @@ void restore_ble_after_failure(bool restore_ble) { ble_manager->setEnabled(true); } +#else + (void)restore_ble; +#endif } struct OtaDownloadContext @@ -1020,6 +1031,7 @@ bool perform_install(std::string& out_error) } bool restore_ble = false; +#if TRAIL_MATE_ENABLE_BLE if (app::hasAppFacade()) { if (ble::BleManager* ble_manager = app::appFacade().getBleManager()) @@ -1033,6 +1045,7 @@ bool perform_install(std::string& out_error) } } } +#endif const bool ok = begin_ota_download(metadata, out_error); if (!ok) diff --git a/platform/esp/arduino_common/src/platform_ui_settings_backup_runtime.cpp b/platform/esp/arduino_common/src/platform_ui_settings_backup_runtime.cpp index 3b2868e0..56f930b4 100644 --- a/platform/esp/arduino_common/src/platform_ui_settings_backup_runtime.cpp +++ b/platform/esp/arduino_common/src/platform_ui_settings_backup_runtime.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include "app/app_config.h" #include "app/app_facade_access.h" @@ -30,6 +29,7 @@ constexpr const char* kBackupTempPath = "/trailmate/settings-backup.tmp"; constexpr const char* kBackupMagic = "trail-mate-settings-backup"; constexpr int kBackupVersion = 1; constexpr std::size_t kMaxBackupBytes = 24 * 1024; +constexpr std::size_t kMaxExtraBlobBytes = 128; enum class ValueType : uint8_t { @@ -179,10 +179,16 @@ int hex_nibble(char ch) return -1; } -bool hex_to_bytes(const char* text, std::vector& out) +bool hex_to_bytes_into(const char* text, + uint8_t* out, + std::size_t capacity, + std::size_t* out_len) { - out.clear(); - if (!text) + if (out_len) + { + *out_len = 0; + } + if (!text || (!out && capacity != 0)) { return false; } @@ -191,17 +197,27 @@ bool hex_to_bytes(const char* text, std::vector& out) { return false; } - out.resize(len / 2); - for (std::size_t index = 0; index < out.size(); ++index) + const std::size_t byte_len = len / 2; + if (byte_len > capacity) + { + return false; + } + for (std::size_t index = 0; index < byte_len; ++index) { const int high = hex_nibble(text[index * 2]); const int low = hex_nibble(text[index * 2 + 1]); if (high < 0 || low < 0) { - out.clear(); return false; } - out[index] = static_cast((high << 4) | low); + if (out) + { + out[index] = static_cast((high << 4) | low); + } + } + if (out_len) + { + *out_len = byte_len; } return true; } @@ -307,24 +323,24 @@ void copy_json_blob(cJSON* object, std::size_t capacity, uint8_t* out_len = nullptr) { + if (!out || capacity == 0) + { + return; + } const char* hex = json_string(object, key); if (!hex) { return; } - std::vector bytes; - if (!hex_to_bytes(hex, bytes) || bytes.size() > capacity) + std::size_t bytes_len = 0; + if (!hex_to_bytes_into(hex, out, capacity, &bytes_len)) { return; } - std::memset(out, 0, capacity); - if (!bytes.empty()) - { - std::memcpy(out, bytes.data(), bytes.size()); - } + std::memset(out + bytes_len, 0, capacity - bytes_len); if (out_len) { - *out_len = static_cast(bytes.size()); + *out_len = static_cast(bytes_len); } } @@ -686,7 +702,6 @@ cJSON* create_app_config_json(const app::AppConfig& config) add_int(object, "mesh_protocol", static_cast(config.mesh_protocol)); add_string(object, "node_name", config.node_name); add_string(object, "short_name", config.short_name); - add_bool(object, "ble_enabled", config.ble_enabled); add_bool(object, "primary_enabled", config.primary_enabled); add_bool(object, "secondary_enabled", config.secondary_enabled); add_bool(object, "primary_uplink_enabled", config.primary_uplink_enabled); @@ -768,7 +783,7 @@ void restore_app_config_json(cJSON* object, app::AppConfig& config) sizeof(config.meshtastic_mqtt_password)); copy_json_string(object, "node_name", config.node_name, sizeof(config.node_name)); copy_json_string(object, "short_name", config.short_name, sizeof(config.short_name)); - config.ble_enabled = json_bool(object, "ble_enabled", config.ble_enabled); + config.ble_enabled = false; config.primary_enabled = json_bool(object, "primary_enabled", config.primary_enabled); config.secondary_enabled = json_bool(object, "secondary_enabled", config.secondary_enabled); config.primary_uplink_enabled = json_bool(object, "primary_uplink_enabled", config.primary_uplink_enabled); @@ -876,13 +891,18 @@ void add_extra_value(cJSON* parent, const ExtraKey& key) } case ValueType::Blob: { - std::vector value; - if (::platform::ui::settings_store::get_blob(key.ns, key.key, value)) + uint8_t value[kMaxExtraBlobBytes] = {}; + std::size_t value_len = 0; + if (::platform::ui::settings_store::get_blob_into(key.ns, + key.key, + value, + sizeof(value), + &value_len)) { - std::string hex(value.size() * 2 + 1, '\0'); - if (bytes_to_hex(value.data(), value.size(), &hex[0], hex.size())) + char hex[kMaxExtraBlobBytes * 2 + 1] = {}; + if (bytes_to_hex(value, value_len, hex, sizeof(hex))) { - add_string(value_object, "value", hex.c_str()); + add_string(value_object, "value", hex); } } break; @@ -927,14 +947,15 @@ void restore_extra_value(const ExtraKey& key, cJSON* value_object) case ValueType::Blob: if (cJSON_IsString(value) && value->valuestring) { - std::vector bytes; - if (hex_to_bytes(value->valuestring, bytes)) + uint8_t bytes[kMaxExtraBlobBytes] = {}; + std::size_t bytes_len = 0; + if (hex_to_bytes_into(value->valuestring, bytes, sizeof(bytes), &bytes_len)) { (void)::platform::ui::settings_store::put_blob( key.ns, key.key, - bytes.empty() ? nullptr : bytes.data(), - bytes.size()); + bytes_len == 0 ? nullptr : bytes, + bytes_len); } } break; diff --git a/platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h b/platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h index 661a0a8c..8ecc18ce 100644 --- a/platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h +++ b/platform/esp/common/include/platform/esp/common/wifi_runtime_impl.h @@ -7,7 +7,6 @@ #include #include #include -#include #include "esp_err.h" #include "esp_event.h" @@ -17,7 +16,11 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#if __has_include("ble/ble_manager.h") +#ifndef TRAIL_MATE_ENABLE_BLE +#define TRAIL_MATE_ENABLE_BLE 0 +#endif + +#if TRAIL_MATE_ENABLE_BLE && __has_include("ble/ble_manager.h") #include "ble/ble_manager.h" #define TRAIL_MATE_WIFI_HAS_BLE_MANAGER 1 #else @@ -217,6 +220,43 @@ void copy_bounded(char* out, std::size_t out_len, const char* text) std::snprintf(out, out_len, "%s", source); } +void insert_scan_result_sorted(ScanResult* out_results, + std::size_t capacity, + std::size_t& out_count, + const wifi_ap_record_t& record) +{ + if (out_results == nullptr || capacity == 0 || record.ssid[0] == 0) + { + return; + } + + ScanResult result{}; + copy_bounded(result.ssid, sizeof(result.ssid), reinterpret_cast(record.ssid)); + result.rssi = record.rssi; + result.requires_password = record.authmode != WIFI_AUTH_OPEN; + + std::size_t insert_at = out_count; + if (out_count < capacity) + { + ++out_count; + } + else if (result.rssi <= out_results[capacity - 1U].rssi) + { + return; + } + else + { + insert_at = capacity - 1U; + } + + while (insert_at > 0 && out_results[insert_at - 1U].rssi < result.rssi) + { + out_results[insert_at] = out_results[insert_at - 1U]; + --insert_at; + } + out_results[insert_at] = result; +} + void set_status_message(const char* message) { copy_bounded(s_runtime.message, sizeof(s_runtime.message), message); @@ -556,6 +596,7 @@ void refresh_runtime_status_message() bool runtime_ble_is_enabled() { +#if TRAIL_MATE_ENABLE_BLE if (!app::hasAppFacade()) { return false; @@ -569,10 +610,14 @@ bool runtime_ble_is_enabled() #endif return app::appFacade().isBleEnabled(); +#else + return false; +#endif } bool pause_runtime_ble_for_wifi() { +#if TRAIL_MATE_ENABLE_BLE if (s_runtime.ble_paused_for_wifi || !app::hasAppFacade()) { return false; @@ -601,10 +646,15 @@ bool pause_runtime_ble_for_wifi() app::appFacade().setBleEnabled(false); s_runtime.ble_paused_for_wifi = true; return true; +#else + s_runtime.ble_paused_for_wifi = false; + return false; +#endif } void restore_runtime_ble_after_wifi(const char* reason) { +#if TRAIL_MATE_ENABLE_BLE if (!s_runtime.ble_paused_for_wifi || !app::hasAppFacade()) { return; @@ -623,6 +673,10 @@ void restore_runtime_ble_after_wifi(const char* reason) std::printf("[WiFi][BLE] restoring BLE runtime facade (%s)\n", reason ? reason : "after Wi-Fi"); app::appFacade().setBleEnabled(true); s_runtime.ble_paused_for_wifi = false; +#else + (void)reason; + s_runtime.ble_paused_for_wifi = false; +#endif } bool ensure_stack_ready() @@ -1245,9 +1299,9 @@ void disconnect() refresh_runtime_status_message(); } -bool scan(std::vector& out_results) +bool scan(ScanResult* out_results, std::size_t capacity, std::size_t& out_count) { - out_results.clear(); + out_count = 0; Config config{}; (void)load_config(config); @@ -1281,42 +1335,27 @@ bool scan(std::vector& out_results) return true; } - std::vector records(ap_count); - if (esp_wifi_scan_get_ap_records(&ap_count, records.data()) != ESP_OK) + uint16_t record_count = std::min(ap_count, kWifiAutoScanMaxRecords); + for (wifi_ap_record_t& record : s_runtime.auto_scan_records) + { + record = wifi_ap_record_t{}; + } + if (esp_wifi_scan_get_ap_records(&record_count, s_runtime.auto_scan_records) != ESP_OK) { set_status_message("Read scan results failed"); return false; } - out_results.reserve(ap_count); - for (const wifi_ap_record_t& record : records) + for (uint16_t i = 0; i < record_count; ++i) { - if (record.ssid[0] == 0) - { - continue; - } - - ScanResult result{}; - copy_bounded(result.ssid, - sizeof(result.ssid), - reinterpret_cast(record.ssid)); - result.rssi = record.rssi; - result.requires_password = record.authmode != WIFI_AUTH_OPEN; - out_results.push_back(result); + insert_scan_result_sorted(out_results, capacity, out_count, s_runtime.auto_scan_records[i]); } - std::sort(out_results.begin(), - out_results.end(), - [](const ScanResult& lhs, const ScanResult& rhs) - { - return lhs.rssi > rhs.rssi; - }); - char buffer[kMaxStatusMessageLength + 1]; std::snprintf(buffer, sizeof(buffer), "Found %u networks", - static_cast(out_results.size())); + static_cast(out_count)); set_status_message(buffer); return true; } diff --git a/platform/esp/idf_common/src/platform_ui_wifi_runtime.cpp b/platform/esp/idf_common/src/platform_ui_wifi_runtime.cpp index ad767e76..f5336c83 100644 --- a/platform/esp/idf_common/src/platform_ui_wifi_runtime.cpp +++ b/platform/esp/idf_common/src/platform_ui_wifi_runtime.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "hostlink/c6/c6_protocol.h" #include "platform/esp/idf_common/wireless_companion/c6_companion.h" @@ -44,6 +43,7 @@ struct RuntimeState { bool profiles_cached = false; Config profiles[kWifiProfileCapacity] = {}; + ScanResult scan_results[kWifiProfileCapacity] = {}; std::size_t profile_count = 0; std::size_t next_profile_index = 0; }; @@ -375,20 +375,52 @@ bool c6_present() return c6::get_c6_companion_status().present; } -void append_scan_results(std::vector& out_results, const c6::C6CompanionStatus& c6_status) +std::size_t copy_scan_results(ScanResult* out_results, + std::size_t capacity, + const c6::C6CompanionStatus& c6_status) { - for (uint8_t i = 0; i < c6_status.wifi_scan_result_count; ++i) + if (out_results == nullptr || capacity == 0) { - ScanResult result{}; + return 0; + } + + std::size_t count = 0; + for (uint8_t i = 0; i < c6_status.wifi_scan_result_count && count < capacity; ++i) + { + ScanResult& result = out_results[count]; + result = ScanResult{}; copy_text(result.ssid, sizeof(result.ssid), c6_status.wifi_scan_results[i].ssid); result.rssi = c6_status.wifi_scan_results[i].rssi; result.requires_password = c6_status.wifi_scan_results[i].authmode != 0; - out_results.push_back(result); + ++count; } + return count; } -bool wait_for_c6_scan(std::vector& out_results) +int best_profile_index_for_scan_results(const ScanResult* results, std::size_t count, int& out_rssi) { + int best_index = -1; + int best_rssi = -128; + for (std::size_t i = 0; i < count; ++i) + { + const int index = profile_index_for_ssid(results[i].ssid); + if (index < 0) + { + continue; + } + if (best_index < 0 || results[i].rssi > best_rssi) + { + best_index = index; + best_rssi = results[i].rssi; + } + } + out_rssi = best_rssi; + return best_index; +} + +bool wait_for_c6_scan(ScanResult* out_results, std::size_t capacity, std::size_t& out_count) +{ + out_count = 0; #if defined(ESP_PLATFORM) && defined(TRAIL_MATE_WIFI_RUNTIME_C6_ASYNC_SCAN) const TickType_t start_ticks = xTaskGetTickCount(); const TickType_t timeout_ticks = pdMS_TO_TICKS(kC6ScanWaitTimeoutMs); @@ -407,7 +439,7 @@ bool wait_for_c6_scan(std::vector& out_results) } else if (saw_scanning || c6_status.wifi_scan_result_count > 0) { - append_scan_results(out_results, c6_status); + out_count = copy_scan_results(out_results, capacity, c6_status); return true; } @@ -415,10 +447,11 @@ bool wait_for_c6_scan(std::vector& out_results) } const auto c6_status = c6::get_c6_companion_status(); - append_scan_results(out_results, c6_status); - return !out_results.empty(); + out_count = copy_scan_results(out_results, capacity, c6_status); + return out_count > 0; #else (void)out_results; + (void)capacity; return false; #endif } @@ -431,9 +464,9 @@ bool select_auto_profile_from_scan(std::size_t& out_index) return false; } - std::vector results; - append_scan_results(results, c6::get_c6_companion_status()); - if (results.empty()) + std::size_t result_count = + copy_scan_results(s_runtime.scan_results, kWifiProfileCapacity, c6::get_c6_companion_status()); + if (result_count == 0) { const bool sent = c6::c6_companion().sendWifiControl(make_control(c6::WifiCommand::Scan)); if (!sent) @@ -441,33 +474,20 @@ bool select_auto_profile_from_scan(std::size_t& out_index) return false; } #if defined(TRAIL_MATE_WIFI_RUNTIME_C6_ASYNC_SCAN) - results.clear(); - if (!wait_for_c6_scan(results)) + if (!wait_for_c6_scan(s_runtime.scan_results, kWifiProfileCapacity, result_count)) { return false; } #endif } - int best_index = -1; int best_rssi = -128; - for (const ScanResult& result : results) - { - const int index = profile_index_for_ssid(result.ssid); - if (index < 0) - { - continue; - } - if (best_index < 0 || result.rssi > best_rssi) - { - best_index = index; - best_rssi = result.rssi; - } - } + const int best_index = + best_profile_index_for_scan_results(s_runtime.scan_results, result_count, best_rssi); if (best_index < 0) { std::printf("[WiFi][C6] auto scan no saved match results=%u profiles=%u\n", - static_cast(results.size()), + static_cast(result_count), static_cast(s_runtime.profile_count)); return false; } @@ -479,7 +499,7 @@ bool select_auto_profile_from_scan(std::size_t& out_index) static_cast(s_runtime.profile_count), s_runtime.profiles[out_index].ssid, best_rssi, - static_cast(results.size())); + static_cast(result_count)); return true; } @@ -574,14 +594,14 @@ void disconnect() } } -bool scan(std::vector& out_results) +bool scan(ScanResult* out_results, std::size_t capacity, std::size_t& out_count) { - out_results.clear(); + out_count = 0; const auto c6_status = c6::get_c6_companion_status(); - append_scan_results(out_results, c6_status); + out_count = copy_scan_results(out_results, capacity, c6_status); if (!c6_status.present) { - return !out_results.empty(); + return out_count > 0; } const bool sent = c6::c6_companion().sendWifiControl(make_control(c6::WifiCommand::Scan)); if (!sent) @@ -589,8 +609,7 @@ bool scan(std::vector& out_results) return false; } #if defined(TRAIL_MATE_WIFI_RUNTIME_C6_ASYNC_SCAN) - out_results.clear(); - return wait_for_c6_scan(out_results); + return wait_for_c6_scan(out_results, capacity, out_count); #else return true; #endif diff --git a/platform/linux/common/src/platform/ui/wifi_runtime.cpp b/platform/linux/common/src/platform/ui/wifi_runtime.cpp index 25d705c1..56626fbe 100644 --- a/platform/linux/common/src/platform/ui/wifi_runtime.cpp +++ b/platform/linux/common/src/platform/ui/wifi_runtime.cpp @@ -95,9 +95,11 @@ void disconnect() { } -bool scan(std::vector& out_results) +bool scan(ScanResult* out_results, std::size_t capacity, std::size_t& out_count) { - out_results.clear(); + (void)out_results; + (void)capacity; + out_count = 0; return false; } diff --git a/platform/nrf52/arduino_common/src/platform_ui_wifi_runtime.cpp b/platform/nrf52/arduino_common/src/platform_ui_wifi_runtime.cpp index 73a35b05..6f93cf63 100644 --- a/platform/nrf52/arduino_common/src/platform_ui_wifi_runtime.cpp +++ b/platform/nrf52/arduino_common/src/platform_ui_wifi_runtime.cpp @@ -45,9 +45,11 @@ void disconnect() { } -bool scan(std::vector& out_results) +bool scan(ScanResult* out_results, std::size_t capacity, std::size_t& out_count) { - out_results.clear(); + (void)out_results; + (void)capacity; + out_count = 0; return false; } diff --git a/platformio.ini b/platformio.ini index 44b4a5e1..cdbbe308 100644 --- a/platformio.ini +++ b/platformio.ini @@ -46,6 +46,8 @@ build_flags = -D SDFAT_FILE_TYPE=3 -D USE_LONG_FILE_NAMES=1 -D USE_UTF8_LONG_NAMES=1 + -D TRAIL_MATE_ENABLE_BLE=0 + -Wl,-u,btInUse -D RADIOLIB_EXCLUDE_RF69 -D RADIOLIB_EXCLUDE_SX1231 -D RADIOLIB_EXCLUDE_RFM2X @@ -82,7 +84,6 @@ lib_deps = lewisxhe/SensorLib @ 0.3.3 mikalhart/TinyGPSPlus @ 1.0.3 earlephilhower/ESP8266Audio @ 2.0.0 - h2zero/NimBLE-Arduino @ 2.2.3 greiman/SdFat @ 2.3.1 nanopb/nanopb @ ^0.4.8 rweather/Crypto @ 0.4.0