From e106573f18108d8ec0ae17837168059b14ad7ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Da=C5=A1i=C4=87?= Date: Tue, 24 Dec 2024 23:19:17 +0100 Subject: [PATCH 01/15] Update lora config translations --- Localizable.xcstrings | 879 +++++++++++++++++-------- Meshtastic/Enums/LoraConfigEnums.swift | 68 +- 2 files changed, 624 insertions(+), 323 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index a5e01356..9cd9081a 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -132,28 +132,6 @@ } } }, - "%@ - 1 Hop" : { - "extractionState" : "stale", - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ - 1 Скок" - } - } - } - }, - "%@ - Direct" : { - "extractionState" : "stale", - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ - Директно" - } - } - } - }, "%@ - No Response" : { "localizations" : { "de" : { @@ -296,17 +274,6 @@ } } }, - "%@ hPa" : { - "extractionState" : "stale", - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%@ hPa" - } - } - } - }, "%@, %@" : { "localizations" : { "en" : { @@ -575,6 +542,23 @@ } } }, + "2.4ghz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "2.4 GHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "2.4 GHz" + } + } + } + }, "7" : { "localizations" : { "sr" : { @@ -2231,6 +2215,23 @@ } } }, + "australia.new.zealand" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Australia / New Zealand" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Аустралија / Нови Зеланд" + } + } + } + }, "automatic.detection" : { "extractionState" : "migrated", "localizations" : { @@ -2390,17 +2391,6 @@ } } }, - "Bad" : { - "extractionState" : "stale", - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Лош" - } - } - } - }, "Bandwidth" : { "localizations" : { "de" : { @@ -4521,14 +4511,38 @@ } }, "Chart" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Графукон" + } + } + } }, "CHG" : { "localizations" : { "sr" : { "stringUnit" : { "state" : "translated", - "value" : "CHG" + "value" : "ПУЊ" + } + } + } + }, + "china" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "China" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кина" } } } @@ -4876,7 +4890,14 @@ } }, "Config" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Конфигурација" + } + } + } }, "config.module.paxcounter.enabled.description" : { "localizations" : { @@ -6811,71 +6832,6 @@ } } }, - "current" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aktuell" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Current" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Actuel" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "נוכחי" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bieżący" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Atual" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aktuell" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Тренутни" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "当前" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "目前" - } - } - } - }, "Current Firmware Version: %@" : { "localizations" : { "de" : { @@ -6930,17 +6886,6 @@ } } }, - "Currently the reccomended way to update ESP32 devices is using the web flasher on a desktop computer from a chrome based browser. It does not work on mobile devices or over BLE." : { - "extractionState" : "stale", - "localizations" : { - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Тренутно препоручени начин за ажурирање ЕСП32 уређаја је коришћење веб флешера на десктоп рачунару из прегледача заснованог на хрому. Не ради на мобилним уређајима или преко BLE-а." - } - } - } - }, "Currently the recommended way to update ESP32 devices is using the web flasher on a desktop computer from a chrome based browser. It does not work on mobile devices or over BLE." : { "localizations" : { "sr" : { @@ -9831,6 +9776,40 @@ } } }, + "european.union.433mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "European Union 433MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Европска унија 433MHz" + } + } + } + }, + "european.union.868mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "European Union 868MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Европска унија 868MHz" + } + } + } + }, "Exchange Positions" : { "localizations" : { "sr" : { @@ -12336,6 +12315,23 @@ } } }, + "india" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "India" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Индија" + } + } + } + }, "Indoor Air Quality" : { "localizations" : { "sr" : { @@ -14787,6 +14783,23 @@ } } }, + "japan" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Japan" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Јапан" + } + } + } + }, "JSON Enabled" : { "localizations" : { "sr" : { @@ -14914,6 +14927,23 @@ } } }, + "korea" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Korea" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кореја" + } + } + } + }, "Last heard" : { "localizations" : { "de" : { @@ -15550,56 +15580,107 @@ } } }, - "Longitude" : { + "long.range.fast" : { + "extractionState" : "manual", "localizations" : { - "de" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "Längengrad" + "value" : "Long Range - Fast" } }, "sr" : { "stringUnit" : { "state" : "translated", - "value" : "Дужина" + "value" : "Дугачки домет - Брзо" } } } }, - "lora" : { + "long.range.moderate" : { + "extractionState" : "manual", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "LoRa" - } - }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "LoRa" + "value" : "Long Range - Moderate" } }, - "fr" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "LoRa" + "value" : "Дугачки домет - Умерено" } - }, - "he" : { + } + } + }, + "long.range.slow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { "stringUnit" : { "state" : "translated", - "value" : "לורה" + "value" : "Long Range - Slow" } }, - "pl" : { + "sr" : { "stringUnit" : { "state" : "translated", - "value" : "LoRa" + "value" : "Дугачки домет - Споро" } - }, - "pt-PT" : { - "stringUnit" : { + } + } + }, + "Longitude" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Längengrad" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дужина" + } + } + } + }, + "lora" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoRa" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoRa" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoRa" + } + }, + "he" : { + "stringUnit" : { + "state" : "translated", + "value" : "לורה" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "LoRa" + } + }, + "pt-PT" : { + "stringUnit" : { "state" : "translated", "value" : "LoRa" } @@ -15772,6 +15853,40 @@ } } }, + "malaysia.433mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Malaysia 433MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Малезија 433MHz" + } + } + } + }, + "malaysia.919mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Malaysia 919MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Малезија 919MHz" + } + } + } + }, "Managed Device" : { "localizations" : { "sr" : { @@ -15951,65 +16066,6 @@ } } }, - "map.recentering" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatic Re-centering" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recentrage automatique" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "מרכז מפה אוטומטית" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatyczne Wyśrodkowywanie" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Re-centralização Automática" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Automatisk Centrering" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Аутоматско поновно центрирање" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "自动重新居中" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "自動重新居中" - } - } - } - }, "map.tiles.delete" : { "extractionState" : "migrated", "localizations" : { @@ -16459,6 +16515,40 @@ } } }, + "medium.range.fast" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medium Range - Fast" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Средњи домет - Брзо" + } + } + } + }, + "medium.range.slow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Medium Range - Slow" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Средњи домет - Споро" + } + } + } + }, "Mesh activity update" : { "localizations" : { "sr" : { @@ -19358,7 +19448,14 @@ } }, "Metric" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Метрика" + } + } + } }, "Minimum Distance" : { "localizations" : { @@ -20258,6 +20355,23 @@ } } }, + "new.zealand.865mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "New Zealand 865MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нови зеланд 865MHz" + } + } + } + }, "Newer firmware is available" : { "localizations" : { "de" : { @@ -21734,6 +21848,57 @@ } } }, + "philippines.433mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Philippines 433MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Филипини 433MHz" + } + } + } + }, + "philippines.868mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Philippines 868MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Филипини 868MHz" + } + } + } + }, + "philippines.915mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Philippines 915MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Филипини 915MHz" + } + } + } + }, "phone.gps" : { "localizations" : { "de" : { @@ -21919,6 +22084,23 @@ } } }, + "please.set.a.region" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Please set a region" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Молимо изаберите регион" + } + } + } + }, "Points of Interest" : { "localizations" : { "sr" : { @@ -25127,6 +25309,23 @@ } } }, + "russia" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Russia" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Русија" + } + } + } + }, "RX Boosted Gain" : { "localizations" : { "sr" : { @@ -25695,71 +25894,6 @@ } } }, - "select.menu.item" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wähle einen Menüeintrag aus" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Select an item from the menu" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sélectioner un item du menu" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "בחר מהתפריט" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wybierz element z menu" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seleciona um opção do menu" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Välj ett alternativ från menyn" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Изабери ставку из менија" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "从菜单选择一个选项" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "從菜單選擇項目" - } - } - } - }, "select.node" : { "localizations" : { "de" : { @@ -26516,7 +26650,14 @@ } }, "Series" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Серије" + } + } + } }, "Server" : { "localizations" : { @@ -26901,6 +27042,57 @@ } } }, + "short.range.fast" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Short Range - Fast" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кратки домет - Брзо" + } + } + } + }, + "short.range.slow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Short Range - Slow" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кратки домет - Споро" + } + } + } + }, + "short.range.turbo" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Short Range - Turbo" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Кратки домет - Турбо" + } + } + } + }, "Show alerts" : { "localizations" : { "de" : { @@ -27055,6 +27247,23 @@ } } }, + "singapore.923mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Singapore 923MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сингапур 923MHz" + } + } + } + }, "Smart Position" : { "localizations" : { "sr" : { @@ -27734,7 +27943,31 @@ } }, "Table" : { - + "localizations" : { + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Табела" + } + } + } + }, + "taiwan" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Taiwan" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тајван" + } + } + } }, "tapback" : { "localizations" : { @@ -28625,6 +28858,23 @@ } } }, + "thailand" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thailand" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тајланд" + } + } + } + }, "The amount of time to wait before we consider your packet as done." : { "localizations" : { "sr" : { @@ -29833,23 +30083,6 @@ } } }, - "Trace route received directly by %@ with a SNR of %@ dB" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Trace route received directly by %1$@ with a SNR of %2$@ dB" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Захтев за праћење руте комуникације директно примљен од %1$@ са SNR од %2$@ dB." - } - } - } - }, "Trace Route Sent" : { "localizations" : { "sr" : { @@ -30047,6 +30280,40 @@ } } }, + "ukraine.433mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukraine 433MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Украјина 433MHz" + } + } + } + }, + "ukraine.868mhz" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ukraine 868MHz" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Украјина 868MHz" + } + } + } + }, "Un-Favorite" : { "localizations" : { "sr" : { @@ -30057,6 +30324,23 @@ } } }, + "united.states" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "United States" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сједињене Америчке државе" + } + } + } + }, "Units displayed on the device screen" : { "localizations" : { "sr" : { @@ -30797,6 +31081,23 @@ } } }, + "very.long.range.slow" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Very Long Range - Slow" + } + }, + "sr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Веома дугачки домет - Споро" + } + } + } + }, "Via Lora" : { "localizations" : { "de" : { diff --git a/Meshtastic/Enums/LoraConfigEnums.swift b/Meshtastic/Enums/LoraConfigEnums.swift index deccde0f..7524b96e 100644 --- a/Meshtastic/Enums/LoraConfigEnums.swift +++ b/Meshtastic/Enums/LoraConfigEnums.swift @@ -79,53 +79,53 @@ enum RegionCodes: Int, CaseIterable, Identifiable { case .lora24: "LORA_24" } } - var id: Int { self.rawValue } - var description: String { + var id: Int { self.rawValue } + var description: String { switch self { case .unset: - return "Please set a region" + return "please.set.a.region".localized case .us: - return "United States" + return "united.states".localized case .eu433: - return "European Union 433mhz" + return "european.union.433mhz".localized case .eu868: - return "European Union 868mhz" + return "european.union.868mhz".localized case .cn: - return "China" + return "china".localized case .jp: - return "Japan" + return "japan".localized case .anz: - return "Australia / New Zealand" + return "australia.new.zealand".localized case .kr: - return "Korea" + return "korea".localized case .tw: - return "Taiwan" + return "taiwan".localized case .ru: - return "Russia" + return "russia".localized case .in: - return "India" + return "india".localized case .nz865: - return "New Zealand 865mhz" + return "new.zealand.865mhz".localized case .th: - return "Thailand" + return "thailand".localized case .ua433: - return "Ukraine 433mhz" + return "ukraine.433mhz".localized case .ua868: - return "Ukraine 868mhz" + return "ukraine.868mhz".localized case .lora24: - return "2.4 GHZ" + return "2.4ghz".localized case .my433: - return "Malaysia 433mhz" + return "malaysia.433mhz".localized case .my919: - return "Malaysia 919mhz" + return "malaysia.919mhz".localized case .sg923: - return "Singapore 923mhz" + return "singapore.923mhz".localized case .ph433: - return "Philippines 433mhz" + return "philippines.433mhz".localized case .ph868: - return "Philippines 868mhz" + return "philippines.868mhz".localized case .ph915: - return "Philippines 915mhz" + return "philippines.915mhz".localized } } var dutyCycle: Int { @@ -289,25 +289,25 @@ enum ModemPresets: Int, CaseIterable, Identifiable { var id: Int { self.rawValue } var description: String { - switch self { + switch self { case .longFast: - return "Long Range - Fast" + return "long.range.fast".localized case .longSlow: - return "Long Range - Slow" + return "long.range.slow".localized case .longModerate: - return "Long Range - Moderate" + return "long.range.moderate".localized case .vLongSlow: - return "Very Long Range - Slow" + return "very.long.range.slow".localized case .medSlow: - return "Medium Range - Slow" + return "medium.range.slow".localized case .medFast: - return "Medium Range - Fast" + return "medium.range.fast".localized case .shortSlow: - return "Short Range - Slow" + return "short.range.slow".localized case .shortFast: - return "Short Range - Fast" + return "short.range.fast".localized case .shortTurbo: - return "Short Range - Turbo" + return "short.range.turbo".localized } } var name: String { From a628ff232a72dd24f8a29a0dfbb92e61d22cc366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nikola=20Da=C5=A1i=C4=87?= Date: Wed, 25 Dec 2024 09:43:08 +0100 Subject: [PATCH 02/15] Update LoraConfigEnums.swift whitespace --- Meshtastic/Enums/LoraConfigEnums.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Enums/LoraConfigEnums.swift b/Meshtastic/Enums/LoraConfigEnums.swift index 7524b96e..691a8298 100644 --- a/Meshtastic/Enums/LoraConfigEnums.swift +++ b/Meshtastic/Enums/LoraConfigEnums.swift @@ -79,8 +79,8 @@ enum RegionCodes: Int, CaseIterable, Identifiable { case .lora24: "LORA_24" } } - var id: Int { self.rawValue } - var description: String { + var id: Int { self.rawValue } + var description: String { switch self { case .unset: return "please.set.a.region".localized @@ -289,7 +289,7 @@ enum ModemPresets: Int, CaseIterable, Identifiable { var id: Int { self.rawValue } var description: String { - switch self { + switch self { case .longFast: return "long.range.fast".localized case .longSlow: From d372cea4da4fd3d67df7e65a5b0860bb5136463d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 27 Dec 2024 09:10:31 -0600 Subject: [PATCH 03/15] Add AlertApp portnum to text message handling --- Meshtastic/Helpers/BLEManager.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 2bba29c7..09ba43b3 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -757,7 +757,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } // Log any other unknownApp calls if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } - case .textMessageApp, .detectionSensorApp: + case .textMessageApp, .detectionSensorApp, .alertApp: + // TODO: Critical alert for alertApp payloads textMessageAppPacket( packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, From 57b9363311eac0e84b14c0fb6411cc7098282d7a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 1 Jan 2025 10:45:25 -0800 Subject: [PATCH 04/15] Bump version, add criticial alerts entitlement --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- Meshtastic/Helpers/BLEManager.swift | 6 +++--- Meshtastic/Meshtastic.entitlements | 2 ++ .../Model/Metrics Visualization/MetricsColumnList.swift | 6 +++--- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 8877bfa9..d2a6449a 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1754,7 +1754,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.14; + MARKETING_VERSION = 2.5.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1788,7 +1788,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.5.14; + MARKETING_VERSION = 2.5.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1820,7 +1820,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.14; + MARKETING_VERSION = 2.5.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1853,7 +1853,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.5.14; + MARKETING_VERSION = 2.5.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 09ba43b3..a70c60ca 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -757,7 +757,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } // Log any other unknownApp calls if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } - case .textMessageApp, .detectionSensorApp, .alertApp: + case .textMessageApp, .detectionSensorApp: // TODO: Critical alert for alertApp payloads textMessageAppPacket( packet: decodedInfo.packet, @@ -766,6 +766,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate context: context, appState: appState ) + case .alertApp: + MeshLogger.log("🕸️ MESH PACKET received for Alert App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .remoteHardwareApp: MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .positionApp: @@ -979,8 +981,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate MeshLogger.log("🕸️ MESH PACKET received for ATAK Plugin App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .powerstressApp: MeshLogger.log("🕸️ MESH PACKET received for Power Stress App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") - case .alertApp: - MeshLogger.log("🕸️ MESH PACKET received for Alert App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce { diff --git a/Meshtastic/Meshtastic.entitlements b/Meshtastic/Meshtastic.entitlements index 241de35a..abcef61d 100644 --- a/Meshtastic/Meshtastic.entitlements +++ b/Meshtastic/Meshtastic.entitlements @@ -2,6 +2,8 @@ + com.apple.developer.usernotifications.critical-alerts + com.apple.developer.associated-domains applinks:meshtastic.org/e/* diff --git a/Meshtastic/Model/Metrics Visualization/MetricsColumnList.swift b/Meshtastic/Model/Metrics Visualization/MetricsColumnList.swift index 0476b6b8..ccb0b758 100644 --- a/Meshtastic/Model/Metrics Visualization/MetricsColumnList.swift +++ b/Meshtastic/Model/Metrics Visualization/MetricsColumnList.swift @@ -79,18 +79,18 @@ class MetricsColumnList: ObservableObject, RandomAccessCollection, RangeReplacea columns.append(newElement) objectWillChange.send() } - + func remove(at index: Int) -> Element { objectWillChange.send() let removedElement = columns.remove(at: index) return removedElement } - + func removeAll() { objectWillChange.send() columns.removeAll() } - + func insert(_ newElement: Element, at index: Int) { objectWillChange.send() columns.insert(newElement, at: index) diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index a424cbf0..ed9728c6 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -30,7 +30,7 @@ struct Connect: View { let notificationCenter = UNUserNotificationCenter.current() notificationCenter.getNotificationSettings(completionHandler: { (settings) in if settings.authorizationStatus == .notDetermined { - UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound, .criticalAlert]) { success, error in if success { Logger.services.info("Notifications are all set!") } else if let error = error { From abaa913d86bf4d4be306ba139c2408197da40727 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 1 Jan 2025 11:20:46 -0800 Subject: [PATCH 05/15] Fix device images for tloras --- .../Contents.json | 0 .../tlora-v2-1-1_6.svg | 0 .../Contents.json | 0 .../tlora-v2-1-1_8.svg | 0 Meshtastic/Extensions/CoreData/UserEntityExtension.swift | 8 ++++---- 5 files changed, 4 insertions(+), 4 deletions(-) rename Meshtastic/Assets.xcassets/{TLORAV2116.imageset => TLORAV211P6.imageset}/Contents.json (100%) rename Meshtastic/Assets.xcassets/{TLORAV2116.imageset => TLORAV211P6.imageset}/tlora-v2-1-1_6.svg (100%) rename Meshtastic/Assets.xcassets/{TLORAV2118.imageset => TLORAV211P8.imageset}/Contents.json (100%) rename Meshtastic/Assets.xcassets/{TLORAV2118.imageset => TLORAV211P8.imageset}/tlora-v2-1-1_8.svg (100%) diff --git a/Meshtastic/Assets.xcassets/TLORAV2116.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json similarity index 100% rename from Meshtastic/Assets.xcassets/TLORAV2116.imageset/Contents.json rename to Meshtastic/Assets.xcassets/TLORAV211P6.imageset/Contents.json diff --git a/Meshtastic/Assets.xcassets/TLORAV2116.imageset/tlora-v2-1-1_6.svg b/Meshtastic/Assets.xcassets/TLORAV211P6.imageset/tlora-v2-1-1_6.svg similarity index 100% rename from Meshtastic/Assets.xcassets/TLORAV2116.imageset/tlora-v2-1-1_6.svg rename to Meshtastic/Assets.xcassets/TLORAV211P6.imageset/tlora-v2-1-1_6.svg diff --git a/Meshtastic/Assets.xcassets/TLORAV2118.imageset/Contents.json b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json similarity index 100% rename from Meshtastic/Assets.xcassets/TLORAV2118.imageset/Contents.json rename to Meshtastic/Assets.xcassets/TLORAV211P8.imageset/Contents.json diff --git a/Meshtastic/Assets.xcassets/TLORAV2118.imageset/tlora-v2-1-1_8.svg b/Meshtastic/Assets.xcassets/TLORAV211P8.imageset/tlora-v2-1-1_8.svg similarity index 100% rename from Meshtastic/Assets.xcassets/TLORAV2118.imageset/tlora-v2-1-1_8.svg rename to Meshtastic/Assets.xcassets/TLORAV211P8.imageset/tlora-v2-1-1_8.svg diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index 57681fd7..4030ea6b 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -71,10 +71,10 @@ extension UserEntity { return "TLORAT3S3EPAPER" case "TLORAT3S3V1": return "TLORAT3S3V1" - case "TLORAV2116": - return "TLORAV2116" - case "TLORAV2118": - return "TLORAV2118" + case "TLORAV211P6": + return "TLORAV211P6" + case "TLORAV211P8": + return "TLORAV211P8" /// Seeed Studio case "SENSECAPINDICATOR": return "SENSECAPINDICATOR" From 3a79e101825e26cc8062ab67e14a74b7c0f1f7bf Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 1 Jan 2025 11:56:25 -0800 Subject: [PATCH 06/15] Use environment variables for default MQTT server connection info for client proxy --- Meshtastic.xcodeproj/project.pbxproj | 4 + Meshtastic/Extensions/EnvironmentValues.swift | 12 +++ .../Helpers/Mqtt/MqttClientProxyManager.swift | 14 ++- .../Settings/Config/Module/MQTTConfig.swift | 85 ++++++++++--------- 4 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 Meshtastic/Extensions/EnvironmentValues.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d2a6449a..bb3dc1f9 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -146,6 +146,7 @@ DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* IntervalEnums.swift */; }; DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; }; DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; }; + DDA28B1A2D25CF7800EF726F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA28B192D25CF6E00EF726F /* EnvironmentValues.swift */; }; DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; }; DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA951592BC6624100CEA535 /* TelemetryWeather.swift */; }; DDA9515C2BC6631200CEA535 /* TelemetryEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */; }; @@ -429,6 +430,7 @@ DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 30.xcdatamodel"; sourceTree = ""; }; DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; + DDA28B192D25CF6E00EF726F /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = ""; }; @@ -1096,6 +1098,7 @@ DDDB445329F8AD1600EE2349 /* Data.swift */, DDDB445129F8ACF900EE2349 /* Date.swift */, DDDB444129F8A88700EE2349 /* Double.swift */, + DDA28B192D25CF6E00EF726F /* EnvironmentValues.swift */, DDDB444329F8A8DD00EE2349 /* Float.swift */, DDDB444D29F8AB0E00EE2349 /* Int.swift */, DDDB444729F8A9C900EE2349 /* String.swift */, @@ -1497,6 +1500,7 @@ DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */, DDDCD5702BB26F5C00BE6B60 /* NodeListFilter.swift in Sources */, + DDA28B1A2D25CF7800EF726F /* EnvironmentValues.swift in Sources */, DD6F65742C6CB80A0053C113 /* View.swift in Sources */, DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, diff --git a/Meshtastic/Extensions/EnvironmentValues.swift b/Meshtastic/Extensions/EnvironmentValues.swift new file mode 100644 index 00000000..236042d4 --- /dev/null +++ b/Meshtastic/Extensions/EnvironmentValues.swift @@ -0,0 +1,12 @@ +// +// EnvironmentValues.swift +// Meshtastic +// +// Copyright(c) by Garth Vander Houwen on 1/1/25. +// +import SwiftUI + +extension EnvironmentValues { + @Entry var publicMqttUsername: String = "meshdev" + @Entry var publicMqttPsk: String = "large4cats" +} diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index 77cd1088..e6a4e8bf 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -6,6 +6,7 @@ // import Foundation +import SwiftUI import CocoaMQTT import OSLog @@ -26,7 +27,7 @@ class MqttClientProxyManager { var debugLog = false func connectFromConfigSettings(node: NodeInfoEntity) { let defaultServerAddress = "mqtt.meshtastic.org" - let useSsl = node.mqttConfig?.tlsEnabled == true + var useSsl = node.mqttConfig?.tlsEnabled == false var defaultServerPort = useSsl ? 8883 : 1883 var host = node.mqttConfig?.address if host == nil || host!.isEmpty { @@ -43,8 +44,15 @@ class MqttClientProxyManager { if let host = host { let port = defaultServerPort - let username = node.mqttConfig?.username - let password = node.mqttConfig?.password + var username = node.mqttConfig?.username + var password = node.mqttConfig?.password + if host == defaultServerAddress { + @Environment(\.publicMqttUsername) var publicMqttUsername + @Environment(\.publicMqttPsk) var publicMqttPsk + username = publicMqttUsername + password = publicMqttPsk + useSsl = false + } let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" let prefix = root! topic = prefix + (supportedVersion ? "/2/e" : "/2/c") + "/#" diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index fc93e6f9..ca54a40e 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -174,51 +174,52 @@ struct MQTTConfig: View { .keyboardType(.default) } .autocorrectionDisabled() - - HStack { - Label("mqtt.username", systemImage: "person.text.rectangle") - TextField("mqtt.username", text: $username) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: username) { - var totalBytes = username.utf8.count - // Only mess with the value if it is too big - while totalBytes > 62 { - username = String(username.dropLast()) - totalBytes = username.utf8.count + if !proxyToClientEnabled && address != "mqtt.meshtastic.org" { + HStack { + Label("mqtt.username", systemImage: "person.text.rectangle") + TextField("mqtt.username", text: $username) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: username) { + var totalBytes = username.utf8.count + // Only mess with the value if it is too big + while totalBytes > 62 { + username = String(username.dropLast()) + totalBytes = username.utf8.count + } + hasChanges = true } - hasChanges = true - } - .foregroundColor(.gray) - } - .keyboardType(.default) - .scrollDismissesKeyboard(.interactively) - HStack { - Label("password", systemImage: "wallet.pass") - TextField("password", text: $password) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: password) { - var totalBytes = password.utf8.count - // Only mess with the value if it is too big - while totalBytes > 62 { - password = String(password.dropLast()) - totalBytes = password.utf8.count + .foregroundColor(.gray) + } + .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) + HStack { + Label("password", systemImage: "wallet.pass") + TextField("password", text: $password) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: password) { + var totalBytes = password.utf8.count + // Only mess with the value if it is too big + while totalBytes > 62 { + password = String(password.dropLast()) + totalBytes = password.utf8.count + } + hasChanges = true } - hasChanges = true - } - .foregroundColor(.gray) - } - .keyboardType(.default) - .scrollDismissesKeyboard(.interactively) - .listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/) - Toggle(isOn: $tlsEnabled) { - Label("TLS Enabled", systemImage: "checkmark.shield.fill") - Text("Your MQTT Server must support TLS. Not available via the public mqtt server.") + .foregroundColor(.gray) + } + .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) + .listRowSeparator(/*@START_MENU_TOKEN@*/.visible/*@END_MENU_TOKEN@*/) + Toggle(isOn: $tlsEnabled) { + Label("TLS Enabled", systemImage: "checkmark.shield.fill") + Text("Your MQTT Server must support TLS. Not available via the public mqtt server.") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } Text("For all Mqtt functionality other than the map report you must also set uplink and downlink for each channel you want to bridge over Mqtt.") .font(.callout) From 7e641cbbd513c5a3af186fc53a008a214088b1cb Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 1 Jan 2025 12:06:07 -0800 Subject: [PATCH 07/15] Add environment variable --- .../xcshareddata/xcschemes/Meshtastic.xcscheme | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme b/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme index 19c6089f..e5a73d1e 100644 --- a/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme +++ b/Meshtastic.xcodeproj/xcshareddata/xcschemes/Meshtastic.xcscheme @@ -77,6 +77,11 @@ value = "oslogToStdio" isEnabled = "YES"> + + Date: Wed, 1 Jan 2025 12:11:56 -0800 Subject: [PATCH 08/15] Use foundation for environment variables --- Meshtastic.xcodeproj/project.pbxproj | 4 ---- Meshtastic/Extensions/EnvironmentValues.swift | 12 ------------ Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift | 8 +++----- 3 files changed, 3 insertions(+), 21 deletions(-) delete mode 100644 Meshtastic/Extensions/EnvironmentValues.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index bb3dc1f9..d2a6449a 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -146,7 +146,6 @@ DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* IntervalEnums.swift */; }; DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; }; DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; }; - DDA28B1A2D25CF7800EF726F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA28B192D25CF6E00EF726F /* EnvironmentValues.swift */; }; DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; }; DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA951592BC6624100CEA535 /* TelemetryWeather.swift */; }; DDA9515C2BC6631200CEA535 /* TelemetryEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */; }; @@ -430,7 +429,6 @@ DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 30.xcdatamodel"; sourceTree = ""; }; DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; - DDA28B192D25CF6E00EF726F /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = ""; }; @@ -1098,7 +1096,6 @@ DDDB445329F8AD1600EE2349 /* Data.swift */, DDDB445129F8ACF900EE2349 /* Date.swift */, DDDB444129F8A88700EE2349 /* Double.swift */, - DDA28B192D25CF6E00EF726F /* EnvironmentValues.swift */, DDDB444329F8A8DD00EE2349 /* Float.swift */, DDDB444D29F8AB0E00EE2349 /* Int.swift */, DDDB444729F8A9C900EE2349 /* String.swift */, @@ -1500,7 +1497,6 @@ DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */, DDDCD5702BB26F5C00BE6B60 /* NodeListFilter.swift in Sources */, - DDA28B1A2D25CF7800EF726F /* EnvironmentValues.swift in Sources */, DD6F65742C6CB80A0053C113 /* View.swift in Sources */, DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, diff --git a/Meshtastic/Extensions/EnvironmentValues.swift b/Meshtastic/Extensions/EnvironmentValues.swift deleted file mode 100644 index 236042d4..00000000 --- a/Meshtastic/Extensions/EnvironmentValues.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// EnvironmentValues.swift -// Meshtastic -// -// Copyright(c) by Garth Vander Houwen on 1/1/25. -// -import SwiftUI - -extension EnvironmentValues { - @Entry var publicMqttUsername: String = "meshdev" - @Entry var publicMqttPsk: String = "large4cats" -} diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index e6a4e8bf..62e07b72 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -6,7 +6,6 @@ // import Foundation -import SwiftUI import CocoaMQTT import OSLog @@ -47,10 +46,9 @@ class MqttClientProxyManager { var username = node.mqttConfig?.username var password = node.mqttConfig?.password if host == defaultServerAddress { - @Environment(\.publicMqttUsername) var publicMqttUsername - @Environment(\.publicMqttPsk) var publicMqttPsk - username = publicMqttUsername - password = publicMqttPsk + + username = ProcessInfo.processInfo.environment["publicMqttUsername"] + password = ProcessInfo.processInfo.environment["publicMqttPsk"] useSsl = false } let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" From ed154c91dd162d2e881d815b2d521a7d3020e2d6 Mon Sep 17 00:00:00 2001 From: HJen-git <111049009+HJen-git@users.noreply.github.com> Date: Sat, 4 Jan 2025 23:40:46 +0100 Subject: [PATCH 09/15] Update Localizable.xcstrings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced incorrect german translation in 2 locations Old: key -> Taste (meaning key as in keyboard) New: key -> Schlüssel (meaning key as in access key) --- Localizable.xcstrings | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index a5e01356..32a65ed4 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -14812,7 +14812,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Taste" + "value" : "Schlüssel" } }, "sr" : { @@ -14838,7 +14838,7 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Tastengröße" + "value" : "Schlüsselgröße" } }, "sr" : { @@ -31333,4 +31333,4 @@ } }, "version" : "1.0" -} \ No newline at end of file +} From 44c73a6c53934113bcc73214ab1693051e247cd8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 7 Jan 2025 16:18:47 -0600 Subject: [PATCH 10/15] Update protobufs --- .../Sources/meshtastic/config.pb.swift | 71 +++++++++++++++++++ .../Sources/meshtastic/device_ui.pb.swift | 18 +++++ .../Sources/meshtastic/mesh.pb.swift | 17 +++++ protobufs | 2 +- 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift index c8c90be7..566b3ef1 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift @@ -263,6 +263,14 @@ public struct Config: Sendable { /// and automatic TAK PLI (position location information) broadcasts. /// Uses position module configuration to determine TAK PLI broadcast interval. case takTracker // = 10 + + /// + /// Description: Will always rebroadcast packets, but will do so after all other modes. + /// Technical Details: Used for router nodes that are intended to provide additional coverage + /// in areas not already covered by other routers, or to bridge around problematic terrain, + /// but should not be given priority over other routers in order to avoid unnecessaraily + /// consuming hops. + case routerLate // = 11 case UNRECOGNIZED(Int) public init() { @@ -282,6 +290,7 @@ public struct Config: Sendable { case 8: self = .clientHidden case 9: self = .lostAndFound case 10: self = .takTracker + case 11: self = .routerLate default: self = .UNRECOGNIZED(rawValue) } } @@ -299,6 +308,7 @@ public struct Config: Sendable { case .clientHidden: return 8 case .lostAndFound: return 9 case .takTracker: return 10 + case .routerLate: return 11 case .UNRECOGNIZED(let i): return i } } @@ -316,6 +326,7 @@ public struct Config: Sendable { .clientHidden, .lostAndFound, .takTracker, + .routerLate, ] } @@ -741,6 +752,10 @@ public struct Config: Sendable { /// rsyslog Server and Port public var rsyslogServer: String = String() + /// + /// Flags for enabling/disabling network protocols + public var enabledProtocols: UInt32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum AddressMode: SwiftProtobuf.Enum, Swift.CaseIterable { @@ -783,6 +798,48 @@ public struct Config: Sendable { } + /// + /// Available flags auxiliary network protocols + public enum ProtocolFlags: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + /// + /// Do not broadcast packets over any network protocol + case noBroadcast // = 0 + + /// + /// Enable broadcasting packets via UDP over the local network + case udpBroadcast // = 1 + case UNRECOGNIZED(Int) + + public init() { + self = .noBroadcast + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .noBroadcast + case 1: self = .udpBroadcast + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .noBroadcast: return 0 + case .udpBroadcast: return 1 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.NetworkConfig.ProtocolFlags] = [ + .noBroadcast, + .udpBroadcast, + ] + + } + public struct IpV4Config: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -2081,6 +2138,7 @@ extension Config.DeviceConfig.Role: SwiftProtobuf._ProtoNameProviding { 8: .same(proto: "CLIENT_HIDDEN"), 9: .same(proto: "LOST_AND_FOUND"), 10: .same(proto: "TAK_TRACKER"), + 11: .same(proto: "ROUTER_LATE"), ] } @@ -2314,6 +2372,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp 7: .standard(proto: "address_mode"), 8: .standard(proto: "ipv4_config"), 9: .standard(proto: "rsyslog_server"), + 10: .standard(proto: "enabled_protocols"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -2330,6 +2389,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp case 7: try { try decoder.decodeSingularEnumField(value: &self.addressMode) }() case 8: try { try decoder.decodeSingularMessageField(value: &self._ipv4Config) }() case 9: try { try decoder.decodeSingularStringField(value: &self.rsyslogServer) }() + case 10: try { try decoder.decodeSingularUInt32Field(value: &self.enabledProtocols) }() default: break } } @@ -2364,6 +2424,9 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if !self.rsyslogServer.isEmpty { try visitor.visitSingularStringField(value: self.rsyslogServer, fieldNumber: 9) } + if self.enabledProtocols != 0 { + try visitor.visitSingularUInt32Field(value: self.enabledProtocols, fieldNumber: 10) + } try unknownFields.traverse(visitor: &visitor) } @@ -2376,6 +2439,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if lhs.addressMode != rhs.addressMode {return false} if lhs._ipv4Config != rhs._ipv4Config {return false} if lhs.rsyslogServer != rhs.rsyslogServer {return false} + if lhs.enabledProtocols != rhs.enabledProtocols {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2388,6 +2452,13 @@ extension Config.NetworkConfig.AddressMode: SwiftProtobuf._ProtoNameProviding { ] } +extension Config.NetworkConfig.ProtocolFlags: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "NO_BROADCAST"), + 1: .same(proto: "UDP_BROADCAST"), + ] +} + extension Config.NetworkConfig.IpV4Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Config.NetworkConfig.protoMessageName + ".IpV4Config" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift index 82c6e834..eaf3951c 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift @@ -133,6 +133,10 @@ public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { /// Norwegian case norwegian // = 14 + /// + /// Slovenian + case slovenian // = 15 + /// /// Simplified Chinese (experimental) case simplifiedChinese // = 30 @@ -163,6 +167,7 @@ public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { case 12: self = .dutch case 13: self = .greek case 14: self = .norwegian + case 15: self = .slovenian case 30: self = .simplifiedChinese case 31: self = .traditionalChinese default: self = .UNRECOGNIZED(rawValue) @@ -186,6 +191,7 @@ public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { case .dutch: return 12 case .greek: return 13 case .norwegian: return 14 + case .slovenian: return 15 case .simplifiedChinese: return 30 case .traditionalChinese: return 31 case .UNRECOGNIZED(let i): return i @@ -209,6 +215,7 @@ public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { .dutch, .greek, .norwegian, + .slovenian, .simplifiedChinese, .traditionalChinese, ] @@ -354,6 +361,10 @@ public struct NodeFilter: Sendable { /// Filter nodes by matching name string public var nodeName: String = String() + /// + /// Filter based on channel + public var channel: Int32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -418,6 +429,7 @@ extension Language: SwiftProtobuf._ProtoNameProviding { 12: .same(proto: "DUTCH"), 13: .same(proto: "GREEK"), 14: .same(proto: "NORWEGIAN"), + 15: .same(proto: "SLOVENIAN"), 30: .same(proto: "SIMPLIFIED_CHINESE"), 31: .same(proto: "TRADITIONAL_CHINESE"), ] @@ -612,6 +624,7 @@ extension NodeFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 4: .standard(proto: "hops_away"), 5: .standard(proto: "position_switch"), 6: .standard(proto: "node_name"), + 7: .same(proto: "channel"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -626,6 +639,7 @@ extension NodeFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio case 4: try { try decoder.decodeSingularInt32Field(value: &self.hopsAway) }() case 5: try { try decoder.decodeSingularBoolField(value: &self.positionSwitch) }() case 6: try { try decoder.decodeSingularStringField(value: &self.nodeName) }() + case 7: try { try decoder.decodeSingularInt32Field(value: &self.channel) }() default: break } } @@ -650,6 +664,9 @@ extension NodeFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if !self.nodeName.isEmpty { try visitor.visitSingularStringField(value: self.nodeName, fieldNumber: 6) } + if self.channel != 0 { + try visitor.visitSingularInt32Field(value: self.channel, fieldNumber: 7) + } try unknownFields.traverse(visitor: &visitor) } @@ -660,6 +677,7 @@ extension NodeFilter: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if lhs.hopsAway != rhs.hopsAway {return false} if lhs.positionSwitch != rhs.positionSwitch {return false} if lhs.nodeName != rhs.nodeName {return false} + if lhs.channel != rhs.channel {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index 72ba0edc..cf3cb4ee 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -2024,6 +2024,15 @@ public struct MeshPacket: @unchecked Sendable { set {_uniqueStorage()._relayNode = newValue} } + /// + /// *Never* sent over the radio links. + /// Timestamp after which this packet may be sent. + /// Set by the firmware internally, clients are not supposed to set this. + public var txAfter: UInt32 { + get {return _storage._txAfter} + set {_uniqueStorage()._txAfter = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable { @@ -4111,6 +4120,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 17: .standard(proto: "pki_encrypted"), 18: .standard(proto: "next_hop"), 19: .standard(proto: "relay_node"), + 20: .standard(proto: "tx_after"), ] fileprivate class _StorageClass { @@ -4132,6 +4142,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio var _pkiEncrypted: Bool = false var _nextHop: UInt32 = 0 var _relayNode: UInt32 = 0 + var _txAfter: UInt32 = 0 #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -4164,6 +4175,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio _pkiEncrypted = source._pkiEncrypted _nextHop = source._nextHop _relayNode = source._relayNode + _txAfter = source._txAfter } } @@ -4220,6 +4232,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio case 17: try { try decoder.decodeSingularBoolField(value: &_storage._pkiEncrypted) }() case 18: try { try decoder.decodeSingularUInt32Field(value: &_storage._nextHop) }() case 19: try { try decoder.decodeSingularUInt32Field(value: &_storage._relayNode) }() + case 20: try { try decoder.decodeSingularUInt32Field(value: &_storage._txAfter) }() default: break } } @@ -4294,6 +4307,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._relayNode != 0 { try visitor.visitSingularUInt32Field(value: _storage._relayNode, fieldNumber: 19) } + if _storage._txAfter != 0 { + try visitor.visitSingularUInt32Field(value: _storage._txAfter, fieldNumber: 20) + } } try unknownFields.traverse(visitor: &visitor) } @@ -4321,6 +4337,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._pkiEncrypted != rhs_storage._pkiEncrypted {return false} if _storage._nextHop != rhs_storage._nextHop {return false} if _storage._relayNode != rhs_storage._relayNode {return false} + if _storage._txAfter != rhs_storage._txAfter {return false} return true } if !storagesAreEqual {return false} diff --git a/protobufs b/protobufs index 2cffaf53..76f806e1 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 2cffaf53e3faf1b6e41a8b8f05312f2f893be413 +Subproject commit 76f806e1bb1e2a7b157a14fadd095775f63db5e4 From a00aa40e36ce062ed0aa34d5a20daab6a8f20a41 Mon Sep 17 00:00:00 2001 From: Brent Petit Date: Wed, 8 Jan 2025 08:58:21 -0600 Subject: [PATCH 11/15] Update traceroute back route display to handle case where route back is not valid --- Meshtastic/Helpers/BLEManager.swift | 29 +++++++++++----------- Meshtastic/Views/Nodes/TraceRouteLog.swift | 2 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 2bba29c7..b542ca0e 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -903,10 +903,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate /// Add the destination node to the end of the route towards string and the beginning of the route back string routeString += "\(traceRoute?.node?.user?.longName ?? "unknown".localized) \((traceRoute?.node?.num ?? 0).toHex()) (\(destinationHop.snr != -32 ? String(destinationHop.snr) : "unknown ".localized)dB)" traceRoute?.routeText = routeString - - traceRoute?.hopsBack = Int32(routingMessage.routeBack.count) + // Default to -1 only fill in if routeBack is valid below + traceRoute?.hopsBack = -1 // Only if hopStart is set and there is an SNR entry if decodedInfo.packet.hopStart > 0 && routingMessage.snrBack.count > 0 { + traceRoute?.hopsBack = Int32(routingMessage.routeBack.count) var routeBackString = "\(traceRoute?.node?.user?.longName ?? "unknown".localized) \((traceRoute?.node?.num ?? 0).toHex()) --> " for (index, node) in routingMessage.routeBack.enumerated() { var hopNode = getNodeInfo(id: Int64(node), context: context) @@ -947,19 +948,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let snrBackLast = Float(routingMessage.snrBack.last ?? -128) / 4 routeBackString += "\(connectedNode.user?.longName ?? String(connectedNode.num.toHex())) (\(snrBackLast != -32 ? String(snrBackLast) : "unknown ".localized)dB)" traceRoute?.routeBackText = routeBackString - traceRoute?.hops = NSOrderedSet(array: hopNodes) - traceRoute?.time = Date() - do { - try context.save() - Logger.data.info("💾 Saved Trace Route") - } catch { - context.rollback() - let nsError = error as NSError - Logger.data.error("Error Updating Core Data TraceRouteHop: \(nsError, privacy: .public)") - } - let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.route %@".localized, routeString) - MeshLogger.log("🪧 \(logString)") } + traceRoute?.hops = NSOrderedSet(array: hopNodes) + traceRoute?.time = Date() + do { + try context.save() + Logger.data.info("💾 Saved Trace Route") + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("Error Updating Core Data TraceRouteHop: \(nsError, privacy: .public)") + } + let logString = String.localizedStringWithFormat("mesh.log.traceroute.received.route %@".localized, routeString) + MeshLogger.log("🪧 \(logString)") } case .neighborinfoApp: if let neighborInfo = try? NeighborInfo(serializedBytes: decodedInfo.packet.decoded.payload) { diff --git a/Meshtastic/Views/Nodes/TraceRouteLog.swift b/Meshtastic/Views/Nodes/TraceRouteLog.swift index 1bdccf46..f10dad58 100644 --- a/Meshtastic/Views/Nodes/TraceRouteLog.swift +++ b/Meshtastic/Views/Nodes/TraceRouteLog.swift @@ -44,7 +44,7 @@ struct TraceRouteLog: View { .font(.caption) } else if route.response { let hopTowardsString = String(localized: "\(route.hopsTowards) Hops") - let hopBackString = String(localized: "\(route.hopsBack) Hops") + let hopBackString = route.hopsBack >= 0 ? String(localized: "\(route.hopsBack) Hops") : String(localized: "unknown") Text("\(routeTime) - \(hopTowardsString) Towards \(hopBackString) Back") .font(.caption) } else if route.sent { From 4062c677ab4349d178a641575e8401694df8ddae Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 8 Jan 2025 11:05:59 -0800 Subject: [PATCH 12/15] Dont use environment variables --- Localizable.xcstrings | 22 +++++++++++++++++++ Meshtastic/Enums/DeviceEnums.swift | 9 +++++++- .../Helpers/Mqtt/MqttClientProxyManager.swift | 4 ++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Localizable.xcstrings b/Localizable.xcstrings index a5e01356..5ed3fbb3 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -8224,6 +8224,17 @@ } } }, + "device.role.name.routerlate" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Router Late" + } + } + } + }, "device.role.name.sensor" : { "extractionState" : "manual", "localizations" : { @@ -8487,6 +8498,17 @@ } } }, + "device.role.routerlate" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Infrastructure node that always rebroadcasts packets once but only after all other modes, ensuring additional coverage for local clusters. Visible in Nodes list." + } + } + } + }, "device.role.sensor" : { "extractionState" : "migrated", "localizations" : { diff --git a/Meshtastic/Enums/DeviceEnums.swift b/Meshtastic/Enums/DeviceEnums.swift index 2128fafa..3f76054f 100644 --- a/Meshtastic/Enums/DeviceEnums.swift +++ b/Meshtastic/Enums/DeviceEnums.swift @@ -22,6 +22,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { case repeater = 4 case router = 2 case routerClient = 3 + case routerLate = 11 var id: Int { self.rawValue } var name: String { @@ -48,6 +49,8 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { return "device.role.name.clientHidden".localized case .lostAndFound: return "device.role.name.lostAndFound".localized + case .routerLate: + return "device.role.name.routerlate".localized } } @@ -75,6 +78,8 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { return "device.role.clienthidden".localized case .lostAndFound: return "device.role.lostandfound".localized + case .routerLate: + return "device.role.routerlate".localized } } @@ -84,7 +89,7 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { return "apps.iphone" case .clientMute: return "speaker.slash" - case .router, .routerClient: + case .router, .routerClient, .routerLate: return "wifi.router" case .repeater: return "repeat" @@ -127,6 +132,8 @@ enum DeviceRoles: Int, CaseIterable, Identifiable { return Config.DeviceConfig.Role.clientHidden case .lostAndFound: return Config.DeviceConfig.Role.lostAndFound + case .routerLate: + return Config.DeviceConfig.Role.routerLate } } } diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index 62e07b72..9c3947c0 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -47,8 +47,8 @@ class MqttClientProxyManager { var password = node.mqttConfig?.password if host == defaultServerAddress { - username = ProcessInfo.processInfo.environment["publicMqttUsername"] - password = ProcessInfo.processInfo.environment["publicMqttPsk"] + // username = ProcessInfo.processInfo.environment["publicMqttUsername"] + // password = ProcessInfo.processInfo.environment["publicMqttPsk"] useSsl = false } let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" From e1f2e34c2e29dc894fd7b687c448009bb03f67d0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 10 Jan 2025 08:32:42 -0800 Subject: [PATCH 13/15] Add criticial alerts functionality to textMessageAppPacket --- Meshtastic/Helpers/BLEManager.swift | 10 ++++++++-- Meshtastic/Helpers/LocalNotificationManager.swift | 4 ++++ Meshtastic/Helpers/MeshPackets.swift | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index a70c60ca..6381a237 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -758,7 +758,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Log any other unknownApp calls if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") } case .textMessageApp, .detectionSensorApp: - // TODO: Critical alert for alertApp payloads textMessageAppPacket( packet: decodedInfo.packet, wantRangeTestPackets: wantRangeTestPackets, @@ -767,7 +766,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate appState: appState ) case .alertApp: - MeshLogger.log("🕸️ MESH PACKET received for Alert App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") + textMessageAppPacket( + packet: decodedInfo.packet, + wantRangeTestPackets: wantRangeTestPackets, + critical: true, + connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), + context: context, + appState: appState + ) case .remoteHardwareApp: MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure")") case .positionApp: diff --git a/Meshtastic/Helpers/LocalNotificationManager.swift b/Meshtastic/Helpers/LocalNotificationManager.swift index 47f64bce..7eb830c3 100644 --- a/Meshtastic/Helpers/LocalNotificationManager.swift +++ b/Meshtastic/Helpers/LocalNotificationManager.swift @@ -67,6 +67,9 @@ class LocalNotificationManager { if notification.userNum != nil { content.userInfo["userNum"] = notification.userNum } + if notification.critical { + content.sound = UNNotificationSound.defaultCritical + } let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) let request = UNNotificationRequest(identifier: notification.id, content: content, trigger: trigger) @@ -101,4 +104,5 @@ struct Notification { var messageId: Int64? var channel: Int32? var userNum: Int64? + var critical: Bool = false } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index fd9ffb8e..917c38c0 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -820,6 +820,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage func textMessageAppPacket( packet: MeshPacket, wantRangeTestPackets: Bool, + critical: Bool = false, connectedNode: Int64, storeForward: Bool = false, context: NSManagedObjectContext, From a31fda92d19d5ea184b306506c3b65e93a7e4f68 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 11 Jan 2025 07:28:59 -0800 Subject: [PATCH 14/15] Make message button match user list --- Meshtastic/Views/Nodes/NodeList.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 6f031018..3bf88f7a 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -93,12 +93,14 @@ struct NodeList: View { ) /// Don't show message, trace route, position exchange or delete context menu items for the connected node if connectedNode.num != node.num { - Button(action: { - if let url = URL(string: "meshtastic:///messages?userNum=\(node.num)") { - UIApplication.shared.open(url) + if (!node.viaMqtt || node.viaMqtt && node.hopsAway == 0) { + Button(action: { + if let url = URL(string: "meshtastic:///messages?userNum=\(node.num)") { + UIApplication.shared.open(url) + } + }) { + Label("Message", systemImage: "message") } - }) { - Label("Message", systemImage: "message") } Button { let traceRouteSent = bleManager.sendTraceRouteRequest( From da1afd9a817a52e72e7ad0a82d4919cc07b76d81 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 11 Jan 2025 10:33:49 -0800 Subject: [PATCH 15/15] Add deviceid from mynodeinfo to core data --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Helpers/MeshPackets.swift | 1 + .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 487 ++++++++++++++++++ 4 files changed, 492 insertions(+), 2 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 48.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d2a6449a..513b7a50 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -429,6 +429,7 @@ DD9A1A912BA2D2D3001E602E /* MeshtasticDataModelV 30.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 30.xcdatamodel"; sourceTree = ""; }; DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; + DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 48.xcdatamodel"; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; DDA951592BC6624100CEA535 /* TelemetryWeather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelemetryWeather.swift; sourceTree = ""; }; DDA9515B2BC6631200CEA535 /* TelemetryEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryEnums.swift; sourceTree = ""; }; @@ -1965,6 +1966,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */, DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */, DD0BE30C2CB785D8000BA445 /* MeshtasticDataModelV 46.xcdatamodel */, DD6D5A342CA13BA600ED3032 /* MeshtasticDataModelV 45.xcdatamodel */, @@ -2013,7 +2015,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */; + currentVersion = DDA28B1B2D32C89200EF726F /* MeshtasticDataModelV 48.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 917c38c0..fb0b381f 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -111,6 +111,7 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO myInfoEntity.peripheralId = peripheralId myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum) myInfoEntity.rebootCount = Int32(myInfo.rebootCount) + myInfoEntity.deviceId = myInfo.deviceID do { try context.save() Logger.data.info("💾 Saved a new myInfo for node: \(myInfo.myNodeNum.toHex(), privacy: .public)") diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 3581f63f..a702965e 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 47.xcdatamodel + MeshtasticDataModelV 48.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 48.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 48.xcdatamodel/contents new file mode 100644 index 00000000..709d5943 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 48.xcdatamodel/contents @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file