diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 4fff08b0..e4d800ba 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -5587,6 +5587,12 @@ } } } + }, + "Bold Heading" : { + + }, + "Bold the heading text on the screen." : { + }, "Broadcast Interval" : { "localizations" : { @@ -9556,70 +9562,6 @@ } } }, - "Decimal Degrees Format" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dezimalgrad Format" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Format décimal pour les degrés" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "פורמט קואורדינטות" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Formato dei gradi decimali" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "十進度形式" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Format Dziesiętny Stopni" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Decimalgrader" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Формат децималних степени" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "十进制" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "十進制" - } - } - } - }, "Default" : { "localizations" : { "de" : { @@ -9718,70 +9660,6 @@ } } }, - "Degrees Minutes Seconds" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Grad Minuten Sekunden" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Degrés Minutes Secondes" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "מעלות דקות שניות" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gradi Minuti Secondi" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "度分秒" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stopnie Minuty Sekundy" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Grader Minuter Sekunder" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Степени Минути Секунде" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "度分秒" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "度分秒" - } - } - } - }, "Delete" : { "localizations" : { "de" : { @@ -16361,40 +16239,6 @@ } } }, - "GPS Format" : { - "localizations" : { - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Formato GPS" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "GPS形式" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "GPS формат" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "GPS 格式" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "GPS 格式" - } - } - } - }, "GPS Receive GPIO" : { "localizations" : { "it" : { @@ -21355,70 +21199,6 @@ } } }, - "Military Grid Reference System" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Militärisches Gitternetz-Referenzsystem" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Military Grid Reference System" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Military Grid Reference System" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sistema di riferimento della griglia militare" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "軍用格子座標系" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Wojskowa siatka odniesienia" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Militärt rutnätsreferenssystem" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Војни референтни систем мреже" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "军事网格参考系统" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "軍事網格參考系統" - } - } - } - }, "Minimum Distance" : { "localizations" : { "de" : { @@ -24771,70 +24551,6 @@ } } }, - "Open Location Code (aka Plus Codes)" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open Location Code (aka Plus Codes)" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open Location Code (alias Plus Codes)" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Open Location Code (aka Plus Codes)" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Codice di localizzazione aperto (alias Codice Plus)" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "オープンロケーションコード(プラスコード)" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Otwarty Kod Lokalizacji (tzw. Plus Kody)" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Öppen Platskod (även känd som Pluskoder)" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Отворени код локације (тј. Плус кодови)" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "开放的位置代码(又称加码)" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "開放位置代碼" - } - } - } - }, "Open Settings" : { "localizations" : { "de" : { @@ -25093,70 +24809,6 @@ } } }, - "Ordnance Survey Grid Reference" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ordnance Survey Gitterreferenz" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ordnance Survey Grid Reference" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ordnance Survey Grid Reference" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Riferimento di griglia Ordnance Survey" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "英国陸地測量部格子座標" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Brytyjski Układ Odniesienia" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ordnance Survey Rutnätsreferens" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Референца мреже Орданс Сурвеја" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "英国国土测量局网格参考" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "英國國土測量局網格參考系統" - } - } - } - }, "OS Log Entry Details" : { "localizations" : { "it" : { @@ -25460,6 +25112,9 @@ } } } + }, + "Override default screen layout." : { + }, "Pairing Mode" : { "localizations" : { @@ -36245,40 +35900,6 @@ } } }, - "The format used to display GPS coordinates on the device screen." : { - "localizations" : { - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Il formato utilizzato per visualizzare le coordinate GPS sullo schermo del dispositivo." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "デバイス画面でGPS座標を表示する形式。" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Формат који се користи за приказивање GPS координата на екрану уређаја." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "用于在设备屏幕上显示 GPS 坐标的格式。" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "用於在裝置螢幕上顯示 GPS 座標的格式。" - } - } - } - }, "The last 4 of the device MAC address will be appended to the short name to set the device's BLE Name. Short name can be up to 4 bytes long." : { "localizations" : { "it" : { @@ -37802,33 +37423,8 @@ } } }, - "Timing & Format" : { - "localizations" : { - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tempi e formati" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "タイミング・フォーマット" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Време и формат" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "時序與格式" - } - } - } + "Timing and Overrides" : { + }, "TLS Enabled" : { "localizations" : { @@ -39028,70 +38624,6 @@ } } }, - "Universal Transverse Mercator" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Universal Transversal Mercator" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Projection Mercator Transverse Universelle" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "Universal Transverse Mercator" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mercatore Universale Trasverso" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ユニバーサル横メルカトル図法" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Uniwersalny Układ Transwersalny Mercatora" - } - }, - "se" : { - "stringUnit" : { - "state" : "translated", - "value" : "Universal Transversal Mercator" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Универзални трансверзални Меркаторов пројекат" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "通用横轴墨卡托投影" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "通用橫軸墨卡托投影" - } - } - } - }, "unknown" : { "localizations" : { "it" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 263cef01..988cb570 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -568,6 +568,7 @@ DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshActivityAttributes.swift; sourceTree = ""; }; DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = ""; }; + DDDF34392E2CB8E600356DC3 /* MeshtasticDataModelV 54.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 54.xcdatamodel"; sourceTree = ""; }; DDDFE73E2D0D48FF0044463C /* IgnoreNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IgnoreNodeButton.swift; sourceTree = ""; }; DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 47.xcdatamodel"; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; @@ -1858,7 +1859,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.6.14; + MARKETING_VERSION = 2.6.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1891,7 +1892,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.6.14; + MARKETING_VERSION = 2.6.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1922,7 +1923,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.6.14; + MARKETING_VERSION = 2.6.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1954,7 +1955,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.6.14; + MARKETING_VERSION = 2.6.15; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2091,6 +2092,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDDF34392E2CB8E600356DC3 /* MeshtasticDataModelV 54.xcdatamodel */, DD1BEF462DFF284C0090CE24 /* MeshtasticDataModelV 53.xcdatamodel */, DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */, DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */, @@ -2145,7 +2147,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD1BEF462DFF284C0090CE24 /* MeshtasticDataModelV 53.xcdatamodel */; + currentVersion = DDDF34392E2CB8E600356DC3 /* MeshtasticDataModelV 54.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Enums/DisplayEnums.swift b/Meshtastic/Enums/DisplayEnums.swift index 664d72a5..44a1cd6e 100644 --- a/Meshtastic/Enums/DisplayEnums.swift +++ b/Meshtastic/Enums/DisplayEnums.swift @@ -85,7 +85,7 @@ enum ScreenCarouselIntervals: Int, CaseIterable, Identifiable { var description: String { switch self { case .off: - return "off".localized + return "off".localized.capitalized case .fifteenSeconds: return "Fifteen Seconds".localized case .thirtySeconds: diff --git a/Meshtastic/Enums/PositionConfigEnums.swift b/Meshtastic/Enums/PositionConfigEnums.swift index 24607df3..895dcda9 100644 --- a/Meshtastic/Enums/PositionConfigEnums.swift +++ b/Meshtastic/Enums/PositionConfigEnums.swift @@ -8,52 +8,6 @@ import Foundation import MeshtasticProtobufs -enum GpsFormats: Int, CaseIterable, Identifiable { - - case gpsFormatDec = 0 - case gpsFormatDms = 1 - case gpsFormatUtm = 2 - case gpsFormatMgrs = 3 - case gpsFormatOlc = 4 - case gpsFormatOsgr = 5 - - var id: Int { self.rawValue } - var description: String { - switch self { - case .gpsFormatDec: - return "Decimal Degrees Format".localized - case .gpsFormatDms: - return "Degrees Minutes Seconds".localized - case .gpsFormatUtm: - return "Universal Transverse Mercator".localized - case .gpsFormatMgrs: - return "Military Grid Reference System".localized - case .gpsFormatOlc: - return "Open Location Code (aka Plus Codes)".localized - case .gpsFormatOsgr: - return "Ordnance Survey Grid Reference".localized - } - } - func protoEnumValue() -> Config.DisplayConfig.GpsCoordinateFormat { - - switch self { - - case .gpsFormatDec: - return Config.DisplayConfig.GpsCoordinateFormat.dec - case .gpsFormatDms: - return Config.DisplayConfig.GpsCoordinateFormat.dms - case .gpsFormatUtm: - return Config.DisplayConfig.GpsCoordinateFormat.utm - case .gpsFormatMgrs: - return Config.DisplayConfig.GpsCoordinateFormat.mgrs - case .gpsFormatOlc: - return Config.DisplayConfig.GpsCoordinateFormat.olc - case .gpsFormatOsgr: - return Config.DisplayConfig.GpsCoordinateFormat.osgr - } - } -} - enum GpsUpdateIntervals: Int, CaseIterable, Identifiable { case thirtySeconds = 30 diff --git a/Meshtastic/Enums/RoutingError.swift b/Meshtastic/Enums/RoutingError.swift index 985794b3..b961cb46 100644 --- a/Meshtastic/Enums/RoutingError.swift +++ b/Meshtastic/Enums/RoutingError.swift @@ -26,6 +26,7 @@ enum RoutingError: Int, CaseIterable, Identifiable { case pkiUnknownPubkey = 35 case adminBadSessionKey = 36 case adminPublicKeyUnauthorized = 37 + case rateLimitExceeded = 38 var id: Int { self.rawValue } var display: String { @@ -63,6 +64,8 @@ enum RoutingError: Int, CaseIterable, Identifiable { return "Bad admin session key".localized case .adminPublicKeyUnauthorized: return "Unauthorized admin public key".localized + case .rateLimitExceeded: + return "Rate Limit Exceeded".localized } } var color: Color { @@ -108,6 +111,8 @@ enum RoutingError: Int, CaseIterable, Identifiable { return true case .adminPublicKeyUnauthorized: return true + case .rateLimitExceeded: + return true } } func protoEnumValue() -> Routing.Error { @@ -146,6 +151,8 @@ enum RoutingError: Int, CaseIterable, Identifiable { return Routing.Error.adminBadSessionKey case .adminPublicKeyUnauthorized: return Routing.Error.adminPublicKeyUnauthorized + case .rateLimitExceeded: + return Routing.Error.rateLimitExceeded } } } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 3c50bd26..d72878eb 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1100,9 +1100,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate isSubscribed = true allowDisconnect = true Logger.mesh.info("🤜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)") - if UserDefaults.firstLaunch { - UserDefaults.showDeviceOnboarding = true - } if sendTime() { } peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) diff --git a/Meshtastic/Helpers/LocationsHandler.swift b/Meshtastic/Helpers/LocationsHandler.swift index ea1a05cc..36fb6c71 100644 --- a/Meshtastic/Helpers/LocationsHandler.swift +++ b/Meshtastic/Helpers/LocationsHandler.swift @@ -40,27 +40,75 @@ import OSLog UserDefaults.standard.set(backgroundActivity, forKey: "BGActivitySessionStarted") } } + // The continuation we will use to asynchronously ask the user permission to track their location. // This is an Optional to ensure it can be nilled out after use. private var permissionContinuation: CheckedContinuation? + + // A flag to prevent multiple concurrent permission requests + private var isRequestingPermission = false + /// Requests "Always" location authorization from the user. /// This method uses Swift's structured concurrency to await the user's decision. + /// It includes a timeout to prevent continuation leaks if the delegate method isn't called. /// - Returns: The `CLAuthorizationStatus` reflecting the user's choice. func requestLocationAlwaysPermissions() async -> CLAuthorizationStatus { + // If a request is already in progress, return the current status immediately. + // This prevents creating multiple continuations and potential leaks. + guard !isRequestingPermission else { + Logger.services.debug("📍 [App] requestLocationAlwaysPermissions called while a request is already active. Returning current status.") + return manager.authorizationStatus + } + // Set flag to indicate a request is in progress + isRequestingPermission = true + return await withCheckedContinuation { continuation in // Store the continuation. self.permissionContinuation = continuation + // Request authorization. The response will come via `locationManagerDidChangeAuthorization`. manager.requestAlwaysAuthorization() + + // Add a timeout to ensure the continuation is always resumed. + // If the delegate method doesn't fire within a reasonable time (e.g., 10 seconds), + // we'll resume the continuation with .notDetermined to prevent a leak. + Task { @MainActor in // Ensure this task runs on the MainActor + do { + try await Task.sleep(for: .seconds(10)) // Wait for 10 seconds + if let currentContinuation = self.permissionContinuation { + // If the continuation hasn't been nilled out yet, it means + // locationManagerDidChangeAuthorization hasn't been called. + Logger.services.warning("📍 [App] Location permission request timed out. Resuming continuation with .notDetermined.") + currentContinuation.resume(returning: .notDetermined) + self.permissionContinuation = nil // Clear the reference + } + } catch is CancellationError { + // This task was cancelled, likely because the main continuation was already resumed + // by locationManagerDidChangeAuthorization. This is expected and safe. + Logger.services.debug("📍 [App] Permission timeout task cancelled.") + } catch { + Logger.services.error("💥 [App] Error in permission timeout task: \(error.localizedDescription, privacy: .public)") + } + } + } + // This defer block ensures `isRequestingPermission` is reset and `permissionContinuation` is nilled out + // regardless of how the `withCheckedContinuation` block exits (success, error, or cancellation). + // It acts as a final cleanup mechanism. + defer { + self.isRequestingPermission = false + // This nil assignment is somewhat redundant with the one in locationManagerDidChangeAuthorization + // and the timeout Task, but it provides an extra layer of safety. + self.permissionContinuation = nil } } + /// Delegate method called when the location authorization status changes. /// - Parameter manager: The CLLocationManager instance. func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { // Ensure the continuation exists before attempting to resume it. - // If it's nil, it means either no request was pending or it was already resumed. + // If it's nil, it means either no request was pending or it was already resumed (e.g., by the timeout). guard let continuation = permissionContinuation else { - Logger.services.debug("📍 [App] locationManagerDidChangeAuthorization called but no permissionContinuation is active.") + Logger.services.debug("📍 [App] locationManagerDidChangeAuthorization called but no permissionContinuation is active or it was already handled.") return } // Resume the continuation with the current authorization status. @@ -69,7 +117,9 @@ import OSLog // This prevents attempting to resume the same continuation multiple times, // which would lead to a runtime crash. self.permissionContinuation = nil + self.isRequestingPermission = false // Reset the flag as the request has completed } + override init() { super.init() self.manager.delegate = self diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index 62b9dc4b..853533e9 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -26,30 +26,29 @@ class MqttClientProxyManager { var topic = "msh" var debugLog = false func connectFromConfigSettings(node: NodeInfoEntity) { + let originalAddress = node.mqttConfig?.address ?? "mqtt.meshtastic.org" let defaultServerAddress = "mqtt.meshtastic.org" - let useSsl = node.mqttConfig?.tlsEnabled == true + var useSsl = node.mqttConfig?.tlsEnabled == true var defaultServerPort = useSsl ? 8883 : 1883 - var host = node.mqttConfig?.address - if host == nil || host!.isEmpty { - host = defaultServerAddress - } else if host != nil && host!.contains(":") { - if let fullHost = host { - host = fullHost.components(separatedBy: ":")[0] - defaultServerPort = Int(fullHost.components(separatedBy: ":")[1]) ?? (useSsl ? 8883 : 1883) - } + var host = originalAddress + if originalAddress.contains(":") { + host = host.components(separatedBy: ":")[0] + defaultServerPort = Int(originalAddress.components(separatedBy: ":")[1]) ?? (useSsl ? 8883 : 1883) } - - if let host = host { - let port = defaultServerPort - let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" - let prefix = root! - topic = prefix + "/2/e" + "/#" - // Require opt in to map report terms to connect - if node.mqttConfig?.mapReportingEnabled ?? false && UserDefaults.mapReportingOptIn || !(node.mqttConfig?.mapReportingEnabled ?? false) { - connect(host: host, port: port, useSsl: useSsl, topic: topic, node: node) - } else { - delegate?.onMqttError(message: "MQTT Map Reporting Terms need to be accepted.") - } + // Require TLS for the public Server + if host.lowercased() == defaultServerAddress { + useSsl = true + defaultServerPort = 8883 + } + let port = defaultServerPort + let root = node.mqttConfig?.root?.count ?? 0 > 0 ? node.mqttConfig?.root : "msh" + let prefix = root! + topic = prefix + "/2/e" + "/#" + // Require opt in to map report terms to connect + if node.mqttConfig?.mapReportingEnabled ?? false && UserDefaults.mapReportingOptIn || !(node.mqttConfig?.mapReportingEnabled ?? false) { + connect(host: host, port: port, useSsl: useSsl, topic: topic, node: node) + } else { + delegate?.onMqttError(message: "MQTT Map Reporting Terms need to be accepted.") } } func connect(host: String, port: Int, useSsl: Bool, topic: String?, node: NodeInfoEntity) { diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 057ab601..b6bc8ca5 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 53.xcdatamodel + MeshtasticDataModelV 54.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 54.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 54.xcdatamodel/contents new file mode 100644 index 00000000..d89e9a0d --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 54.xcdatamodel/contents @@ -0,0 +1,505 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 3c762a49..b69c8442 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -610,7 +610,6 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses if fetchedNode[0].displayConfig == nil { let newDisplayConfig = DisplayConfigEntity(context: context) - newDisplayConfig.gpsFormat = Int32(config.gpsFormat.rawValue) newDisplayConfig.screenOnSeconds = Int32(truncatingIfNeeded: config.screenOnSecs) newDisplayConfig.screenCarouselInterval = Int32(truncatingIfNeeded: config.autoScreenCarouselSecs) newDisplayConfig.compassNorthTop = config.compassNorthTop @@ -622,8 +621,6 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses newDisplayConfig.use12HClock = config.use12HClock fetchedNode[0].displayConfig = newDisplayConfig } else { - - fetchedNode[0].displayConfig?.gpsFormat = Int32(config.gpsFormat.rawValue) fetchedNode[0].displayConfig?.screenOnSeconds = Int32(truncatingIfNeeded: config.screenOnSecs) fetchedNode[0].displayConfig?.screenCarouselInterval = Int32(truncatingIfNeeded: config.autoScreenCarouselSecs) fetchedNode[0].displayConfig?.compassNorthTop = config.compassNorthTop @@ -631,8 +628,8 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses fetchedNode[0].displayConfig?.oledType = Int32(config.oled.rawValue) fetchedNode[0].displayConfig?.displayMode = Int32(config.displaymode.rawValue) fetchedNode[0].displayConfig?.units = Int32(config.units.rawValue) - fetchedNode[0].displayConfig?.use12HClock = config.use12HClock fetchedNode[0].displayConfig?.headingBold = config.headingBold + fetchedNode[0].displayConfig?.use12HClock = config.use12HClock } if sessionPasskey != nil { fetchedNode[0].sessionPasskey = sessionPasskey diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index d57a0792..5c1b285b 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -72,7 +72,7 @@ struct ContentView: View { isShowingDeviceOnboardingFlow = true } } - .onChange(of: UserDefaults.showDeviceOnboarding) { newValue in + .onChange(of: UserDefaults.showDeviceOnboarding) {_, newValue in isShowingDeviceOnboardingFlow = newValue } } diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 682bfd45..9a10f7bd 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -28,60 +28,44 @@ struct DisplayConfig: View { @State var displayMode = 0 @State var units = 0 @State var use12HourClock = false + @State var headingBold = false var body: some View { Form { ConfigHeader(title: "Display", config: \.displayConfig, node: node, onAppear: setDisplayValues) Section(header: Text("Device Screen")) { - VStack(alignment: .leading) { - Picker("Display Mode", selection: $displayMode ) { - ForEach(DisplayModes.allCases) { dm in - Text(dm.description) - } - } - Text("Override automatic OLED screen detection.") - .foregroundColor(.gray) - .font(.callout) - } - .pickerStyle(DefaultPickerStyle()) Toggle(isOn: $compassNorthTop) { Label("Always point north", systemImage: "location.north.circle") Text("The compass heading on the screen outside of the circle will always point north.") } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .tint(Color.accentColor) - Toggle(isOn: $wakeOnTapOrMotion) { - Label("Wake Screen on tap or motion", systemImage: "gyroscope") - Text("Requires that there be an accelerometer on your device.") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - - Toggle(isOn: $flipScreen) { - Label("Flip Screen", systemImage: "pip.swap") - Text("Flip screen vertically") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - - VStack(alignment: .leading) { - Picker("OLED Type", selection: $oledType ) { - ForEach(OledTypes.allCases) { ot in - Text(ot.description) - } - } - Text("Override automatic OLED screen detection.") - .foregroundColor(.gray) - .font(.callout) - } - .pickerStyle(DefaultPickerStyle()) Toggle(isOn: $use12HourClock) { Label("12 Hour Clock", systemImage: "clock") Text("Sets the screen clock format to 12-hour.") } .tint(Color.accentColor) + + Toggle(isOn: $headingBold) { + Label("Bold Heading", systemImage: "bold") + Text("Bold the heading text on the screen.") + } + .tint(Color.accentColor) + VStack(alignment: .leading) { + Picker("Display Units", selection: $units ) { + ForEach(Units.allCases) { un in + Text(un.description) + } + } + Text("Units displayed on the device screen") + .foregroundColor(.gray) + .font(.callout) + } + .pickerStyle(DefaultPickerStyle()) } - Section(header: Text("Timing & Format")) { + Section(header: Text("Timing and Overrides")) { VStack(alignment: .leading) { Picker("Screen on for", selection: $screenOnSeconds ) { ForEach(ScreenOnIntervals.allCases) { soi in @@ -107,25 +91,36 @@ struct DisplayConfig: View { } .pickerStyle(DefaultPickerStyle()) + Toggle(isOn: $wakeOnTapOrMotion) { + Label("Wake Screen on tap or motion", systemImage: "gyroscope") + Text("Requires that there be an accelerometer on your device.") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $flipScreen) { + Label("Flip Screen", systemImage: "pip.swap") + Text("Flip screen vertically") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + VStack(alignment: .leading) { - Picker("GPS Format", selection: $gpsFormat ) { - ForEach(GpsFormats.allCases) { lu in - Text(lu.description) + Picker("Display Mode", selection: $displayMode ) { + ForEach(DisplayModes.allCases) { dm in + Text(dm.description) } } - Text("The format used to display GPS coordinates on the device screen.") + Text("Override default screen layout.") .foregroundColor(.gray) .font(.callout) } .pickerStyle(DefaultPickerStyle()) - VStack(alignment: .leading) { - Picker("Display Units", selection: $units ) { - ForEach(Units.allCases) { un in - Text(un.description) + Picker("OLED Type", selection: $oledType ) { + ForEach(OledTypes.allCases) { ot in + Text(ot.description) } } - Text("Units displayed on the device screen") + Text("Override automatic OLED screen detection.") .foregroundColor(.gray) .font(.callout) } @@ -138,7 +133,6 @@ struct DisplayConfig: View { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) if connectedNode != nil { var dc = Config.DisplayConfig() - dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() dc.screenOnSecs = UInt32(screenOnSeconds) dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval) dc.compassNorthTop = compassNorthTop @@ -148,6 +142,7 @@ struct DisplayConfig: View { dc.displaymode = DisplayModes(rawValue: displayMode)!.protoEnumValue() dc.units = Units(rawValue: units)!.protoEnumValue() dc.use12HClock = use12HourClock + dc.headingBold = headingBold let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!) if adminMessageId > 0 { @@ -203,9 +198,6 @@ struct DisplayConfig: View { .onChange(of: wakeOnTapOrMotion) { oldWakeOnTapOrMotion, newWakeOnTapOrMotion in if oldWakeOnTapOrMotion != newWakeOnTapOrMotion && newWakeOnTapOrMotion != node?.displayConfig?.wakeOnTapOrMotion { hasChanges = true } } - .onChange(of: gpsFormat) { oldGpsFormat, newGpsFormat in - if oldGpsFormat != newGpsFormat && newGpsFormat != node?.displayConfig?.gpsFormat ?? -1 { hasChanges = true } - } .onChange(of: flipScreen) { oldFlipScreen, newFlipScreen in if oldFlipScreen != newFlipScreen && newFlipScreen != node?.displayConfig?.flipScreen { hasChanges = true } } @@ -221,18 +213,21 @@ struct DisplayConfig: View { .onChange(of: use12HourClock) { oldUse12HourClock, newUse12HourClock in if oldUse12HourClock != newUse12HourClock && newUse12HourClock != node?.displayConfig?.use12HClock { hasChanges = true } } + .onChange(of: headingBold) { oldHeadingBold, newHeadingBold in + if oldHeadingBold != newHeadingBold && newHeadingBold != node?.displayConfig?.headingBold { hasChanges = true } + } } func setDisplayValues() { - self.gpsFormat = Int(node?.displayConfig?.gpsFormat ?? 0) - self.screenOnSeconds = Int(node?.displayConfig?.screenOnSeconds ?? 0) - self.screenCarouselInterval = Int(node?.displayConfig?.screenCarouselInterval ?? 0) - self.compassNorthTop = node?.displayConfig?.compassNorthTop ?? false - self.wakeOnTapOrMotion = node?.displayConfig?.wakeOnTapOrMotion ?? false - self.flipScreen = node?.displayConfig?.flipScreen ?? false - self.oledType = Int(node?.displayConfig?.oledType ?? 0) - self.displayMode = Int(node?.displayConfig?.displayMode ?? 0) - self.units = Int(node?.displayConfig?.units ?? 0) - self.use12HourClock = node?.displayConfig?.use12HClock ?? false - self.hasChanges = node?.displayConfig?.use12HClock ?? false + self.screenOnSeconds = Int(node?.displayConfig?.screenOnSeconds ?? 0) + self.screenCarouselInterval = Int(node?.displayConfig?.screenCarouselInterval ?? 0) + self.compassNorthTop = node?.displayConfig?.compassNorthTop ?? false + self.wakeOnTapOrMotion = node?.displayConfig?.wakeOnTapOrMotion ?? false + self.flipScreen = node?.displayConfig?.flipScreen ?? false + self.oledType = Int(node?.displayConfig?.oledType ?? 0) + self.displayMode = Int(node?.displayConfig?.displayMode ?? 0) + self.units = Int(node?.displayConfig?.units ?? 0) + self.headingBold = node?.displayConfig?.headingBold ?? false + self.use12HourClock = node?.displayConfig?.use12HClock ?? false + self.hasChanges = false } } diff --git a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift index 63797080..ade2e8f3 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift @@ -963,7 +963,10 @@ public struct Config: Sendable { public var screenOnSecs: UInt32 = 0 /// + /// Deprecated in 2.7.4: Unused /// How the GPS coordinates are formatted on the OLED screen. + /// + /// NOTE: This field was marked as deprecated in the .proto file. public var gpsFormat: Config.DisplayConfig.GpsCoordinateFormat = .dec /// diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index f46740fb..81bf379b 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -1746,6 +1746,11 @@ public struct Routing: Sendable { /// /// Admin packet sent using PKC, but not from a public key on the admin key list case adminPublicKeyUnauthorized // = 37 + + /// + /// Airtime fairness rate limit exceeded for a packet + /// This typically enforced per portnum and is used to prevent a single node from monopolizing airtime + case rateLimitExceeded // = 38 case UNRECOGNIZED(Int) public init() { @@ -1770,6 +1775,7 @@ public struct Routing: Sendable { case 35: self = .pkiUnknownPubkey case 36: self = .adminBadSessionKey case 37: self = .adminPublicKeyUnauthorized + case 38: self = .rateLimitExceeded default: self = .UNRECOGNIZED(rawValue) } } @@ -1792,6 +1798,7 @@ public struct Routing: Sendable { case .pkiUnknownPubkey: return 35 case .adminBadSessionKey: return 36 case .adminPublicKeyUnauthorized: return 37 + case .rateLimitExceeded: return 38 case .UNRECOGNIZED(let i): return i } } @@ -1814,6 +1821,7 @@ public struct Routing: Sendable { .pkiUnknownPubkey, .adminBadSessionKey, .adminPublicKeyUnauthorized, + .rateLimitExceeded, ] } @@ -4221,6 +4229,7 @@ extension Routing.Error: SwiftProtobuf._ProtoNameProviding { 35: .same(proto: "PKI_UNKNOWN_PUBKEY"), 36: .same(proto: "ADMIN_BAD_SESSION_KEY"), 37: .same(proto: "ADMIN_PUBLIC_KEY_UNAUTHORIZED"), + 38: .same(proto: "RATE_LIMIT_EXCEEDED"), ] } diff --git a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift index c339fdbc..f0e6df4c 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift @@ -188,6 +188,14 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { /// /// ADS1X15 ADC case ads1X15 // = 40 + + /// + /// ADS1X15 ADC_ALT + case ads1X15Alt // = 41 + + /// + /// Sensirion SFA30 Formaldehyde sensor + case sfa30 // = 42 case UNRECOGNIZED(Int) public init() { @@ -237,6 +245,8 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { case 38: self = .max17261 case 39: self = .pct2075 case 40: self = .ads1X15 + case 41: self = .ads1X15Alt + case 42: self = .sfa30 default: self = .UNRECOGNIZED(rawValue) } } @@ -284,6 +294,8 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { case .max17261: return 38 case .pct2075: return 39 case .ads1X15: return 40 + case .ads1X15Alt: return 41 + case .sfa30: return 42 case .UNRECOGNIZED(let i): return i } } @@ -331,6 +343,8 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { .max17261, .pct2075, .ads1X15, + .ads1X15Alt, + .sfa30, ] } @@ -873,7 +887,7 @@ public struct PowerMetrics: Sendable { /// /// Air quality metrics -public struct AirQualityMetrics: Sendable { +public struct AirQualityMetrics: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -881,187 +895,206 @@ public struct AirQualityMetrics: Sendable { /// /// Concentration Units Standard PM1.0 public var pm10Standard: UInt32 { - get {return _pm10Standard ?? 0} - set {_pm10Standard = newValue} + get {return _storage._pm10Standard ?? 0} + set {_uniqueStorage()._pm10Standard = newValue} } /// Returns true if `pm10Standard` has been explicitly set. - public var hasPm10Standard: Bool {return self._pm10Standard != nil} + public var hasPm10Standard: Bool {return _storage._pm10Standard != nil} /// Clears the value of `pm10Standard`. Subsequent reads from it will return its default value. - public mutating func clearPm10Standard() {self._pm10Standard = nil} + public mutating func clearPm10Standard() {_uniqueStorage()._pm10Standard = nil} /// /// Concentration Units Standard PM2.5 public var pm25Standard: UInt32 { - get {return _pm25Standard ?? 0} - set {_pm25Standard = newValue} + get {return _storage._pm25Standard ?? 0} + set {_uniqueStorage()._pm25Standard = newValue} } /// Returns true if `pm25Standard` has been explicitly set. - public var hasPm25Standard: Bool {return self._pm25Standard != nil} + public var hasPm25Standard: Bool {return _storage._pm25Standard != nil} /// Clears the value of `pm25Standard`. Subsequent reads from it will return its default value. - public mutating func clearPm25Standard() {self._pm25Standard = nil} + public mutating func clearPm25Standard() {_uniqueStorage()._pm25Standard = nil} /// /// Concentration Units Standard PM10.0 public var pm100Standard: UInt32 { - get {return _pm100Standard ?? 0} - set {_pm100Standard = newValue} + get {return _storage._pm100Standard ?? 0} + set {_uniqueStorage()._pm100Standard = newValue} } /// Returns true if `pm100Standard` has been explicitly set. - public var hasPm100Standard: Bool {return self._pm100Standard != nil} + public var hasPm100Standard: Bool {return _storage._pm100Standard != nil} /// Clears the value of `pm100Standard`. Subsequent reads from it will return its default value. - public mutating func clearPm100Standard() {self._pm100Standard = nil} + public mutating func clearPm100Standard() {_uniqueStorage()._pm100Standard = nil} /// /// Concentration Units Environmental PM1.0 public var pm10Environmental: UInt32 { - get {return _pm10Environmental ?? 0} - set {_pm10Environmental = newValue} + get {return _storage._pm10Environmental ?? 0} + set {_uniqueStorage()._pm10Environmental = newValue} } /// Returns true if `pm10Environmental` has been explicitly set. - public var hasPm10Environmental: Bool {return self._pm10Environmental != nil} + public var hasPm10Environmental: Bool {return _storage._pm10Environmental != nil} /// Clears the value of `pm10Environmental`. Subsequent reads from it will return its default value. - public mutating func clearPm10Environmental() {self._pm10Environmental = nil} + public mutating func clearPm10Environmental() {_uniqueStorage()._pm10Environmental = nil} /// /// Concentration Units Environmental PM2.5 public var pm25Environmental: UInt32 { - get {return _pm25Environmental ?? 0} - set {_pm25Environmental = newValue} + get {return _storage._pm25Environmental ?? 0} + set {_uniqueStorage()._pm25Environmental = newValue} } /// Returns true if `pm25Environmental` has been explicitly set. - public var hasPm25Environmental: Bool {return self._pm25Environmental != nil} + public var hasPm25Environmental: Bool {return _storage._pm25Environmental != nil} /// Clears the value of `pm25Environmental`. Subsequent reads from it will return its default value. - public mutating func clearPm25Environmental() {self._pm25Environmental = nil} + public mutating func clearPm25Environmental() {_uniqueStorage()._pm25Environmental = nil} /// /// Concentration Units Environmental PM10.0 public var pm100Environmental: UInt32 { - get {return _pm100Environmental ?? 0} - set {_pm100Environmental = newValue} + get {return _storage._pm100Environmental ?? 0} + set {_uniqueStorage()._pm100Environmental = newValue} } /// Returns true if `pm100Environmental` has been explicitly set. - public var hasPm100Environmental: Bool {return self._pm100Environmental != nil} + public var hasPm100Environmental: Bool {return _storage._pm100Environmental != nil} /// Clears the value of `pm100Environmental`. Subsequent reads from it will return its default value. - public mutating func clearPm100Environmental() {self._pm100Environmental = nil} + public mutating func clearPm100Environmental() {_uniqueStorage()._pm100Environmental = nil} /// /// 0.3um Particle Count public var particles03Um: UInt32 { - get {return _particles03Um ?? 0} - set {_particles03Um = newValue} + get {return _storage._particles03Um ?? 0} + set {_uniqueStorage()._particles03Um = newValue} } /// Returns true if `particles03Um` has been explicitly set. - public var hasParticles03Um: Bool {return self._particles03Um != nil} + public var hasParticles03Um: Bool {return _storage._particles03Um != nil} /// Clears the value of `particles03Um`. Subsequent reads from it will return its default value. - public mutating func clearParticles03Um() {self._particles03Um = nil} + public mutating func clearParticles03Um() {_uniqueStorage()._particles03Um = nil} /// /// 0.5um Particle Count public var particles05Um: UInt32 { - get {return _particles05Um ?? 0} - set {_particles05Um = newValue} + get {return _storage._particles05Um ?? 0} + set {_uniqueStorage()._particles05Um = newValue} } /// Returns true if `particles05Um` has been explicitly set. - public var hasParticles05Um: Bool {return self._particles05Um != nil} + public var hasParticles05Um: Bool {return _storage._particles05Um != nil} /// Clears the value of `particles05Um`. Subsequent reads from it will return its default value. - public mutating func clearParticles05Um() {self._particles05Um = nil} + public mutating func clearParticles05Um() {_uniqueStorage()._particles05Um = nil} /// /// 1.0um Particle Count public var particles10Um: UInt32 { - get {return _particles10Um ?? 0} - set {_particles10Um = newValue} + get {return _storage._particles10Um ?? 0} + set {_uniqueStorage()._particles10Um = newValue} } /// Returns true if `particles10Um` has been explicitly set. - public var hasParticles10Um: Bool {return self._particles10Um != nil} + public var hasParticles10Um: Bool {return _storage._particles10Um != nil} /// Clears the value of `particles10Um`. Subsequent reads from it will return its default value. - public mutating func clearParticles10Um() {self._particles10Um = nil} + public mutating func clearParticles10Um() {_uniqueStorage()._particles10Um = nil} /// /// 2.5um Particle Count public var particles25Um: UInt32 { - get {return _particles25Um ?? 0} - set {_particles25Um = newValue} + get {return _storage._particles25Um ?? 0} + set {_uniqueStorage()._particles25Um = newValue} } /// Returns true if `particles25Um` has been explicitly set. - public var hasParticles25Um: Bool {return self._particles25Um != nil} + public var hasParticles25Um: Bool {return _storage._particles25Um != nil} /// Clears the value of `particles25Um`. Subsequent reads from it will return its default value. - public mutating func clearParticles25Um() {self._particles25Um = nil} + public mutating func clearParticles25Um() {_uniqueStorage()._particles25Um = nil} /// /// 5.0um Particle Count public var particles50Um: UInt32 { - get {return _particles50Um ?? 0} - set {_particles50Um = newValue} + get {return _storage._particles50Um ?? 0} + set {_uniqueStorage()._particles50Um = newValue} } /// Returns true if `particles50Um` has been explicitly set. - public var hasParticles50Um: Bool {return self._particles50Um != nil} + public var hasParticles50Um: Bool {return _storage._particles50Um != nil} /// Clears the value of `particles50Um`. Subsequent reads from it will return its default value. - public mutating func clearParticles50Um() {self._particles50Um = nil} + public mutating func clearParticles50Um() {_uniqueStorage()._particles50Um = nil} /// /// 10.0um Particle Count public var particles100Um: UInt32 { - get {return _particles100Um ?? 0} - set {_particles100Um = newValue} + get {return _storage._particles100Um ?? 0} + set {_uniqueStorage()._particles100Um = newValue} } /// Returns true if `particles100Um` has been explicitly set. - public var hasParticles100Um: Bool {return self._particles100Um != nil} + public var hasParticles100Um: Bool {return _storage._particles100Um != nil} /// Clears the value of `particles100Um`. Subsequent reads from it will return its default value. - public mutating func clearParticles100Um() {self._particles100Um = nil} + public mutating func clearParticles100Um() {_uniqueStorage()._particles100Um = nil} /// /// CO2 concentration in ppm public var co2: UInt32 { - get {return _co2 ?? 0} - set {_co2 = newValue} + get {return _storage._co2 ?? 0} + set {_uniqueStorage()._co2 = newValue} } /// Returns true if `co2` has been explicitly set. - public var hasCo2: Bool {return self._co2 != nil} + public var hasCo2: Bool {return _storage._co2 != nil} /// Clears the value of `co2`. Subsequent reads from it will return its default value. - public mutating func clearCo2() {self._co2 = nil} + public mutating func clearCo2() {_uniqueStorage()._co2 = nil} /// /// CO2 sensor temperature in degC public var co2Temperature: Float { - get {return _co2Temperature ?? 0} - set {_co2Temperature = newValue} + get {return _storage._co2Temperature ?? 0} + set {_uniqueStorage()._co2Temperature = newValue} } /// Returns true if `co2Temperature` has been explicitly set. - public var hasCo2Temperature: Bool {return self._co2Temperature != nil} + public var hasCo2Temperature: Bool {return _storage._co2Temperature != nil} /// Clears the value of `co2Temperature`. Subsequent reads from it will return its default value. - public mutating func clearCo2Temperature() {self._co2Temperature = nil} + public mutating func clearCo2Temperature() {_uniqueStorage()._co2Temperature = nil} /// /// CO2 sensor relative humidity in % public var co2Humidity: Float { - get {return _co2Humidity ?? 0} - set {_co2Humidity = newValue} + get {return _storage._co2Humidity ?? 0} + set {_uniqueStorage()._co2Humidity = newValue} } /// Returns true if `co2Humidity` has been explicitly set. - public var hasCo2Humidity: Bool {return self._co2Humidity != nil} + public var hasCo2Humidity: Bool {return _storage._co2Humidity != nil} /// Clears the value of `co2Humidity`. Subsequent reads from it will return its default value. - public mutating func clearCo2Humidity() {self._co2Humidity = nil} + public mutating func clearCo2Humidity() {_uniqueStorage()._co2Humidity = nil} + + /// + /// Formaldehyde sensor formaldehyde concentration in ppb + public var formFormaldehyde: Float { + get {return _storage._formFormaldehyde ?? 0} + set {_uniqueStorage()._formFormaldehyde = newValue} + } + /// Returns true if `formFormaldehyde` has been explicitly set. + public var hasFormFormaldehyde: Bool {return _storage._formFormaldehyde != nil} + /// Clears the value of `formFormaldehyde`. Subsequent reads from it will return its default value. + public mutating func clearFormFormaldehyde() {_uniqueStorage()._formFormaldehyde = nil} + + /// + /// Formaldehyde sensor relative humidity in %RH + public var formHumidity: Float { + get {return _storage._formHumidity ?? 0} + set {_uniqueStorage()._formHumidity = newValue} + } + /// Returns true if `formHumidity` has been explicitly set. + public var hasFormHumidity: Bool {return _storage._formHumidity != nil} + /// Clears the value of `formHumidity`. Subsequent reads from it will return its default value. + public mutating func clearFormHumidity() {_uniqueStorage()._formHumidity = nil} + + /// + /// Formaldehyde sensor temperature in degrees Celsius + public var formTemperature: Float { + get {return _storage._formTemperature ?? 0} + set {_uniqueStorage()._formTemperature = newValue} + } + /// Returns true if `formTemperature` has been explicitly set. + public var hasFormTemperature: Bool {return _storage._formTemperature != nil} + /// Clears the value of `formTemperature`. Subsequent reads from it will return its default value. + public mutating func clearFormTemperature() {_uniqueStorage()._formTemperature = nil} public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} - fileprivate var _pm10Standard: UInt32? = nil - fileprivate var _pm25Standard: UInt32? = nil - fileprivate var _pm100Standard: UInt32? = nil - fileprivate var _pm10Environmental: UInt32? = nil - fileprivate var _pm25Environmental: UInt32? = nil - fileprivate var _pm100Environmental: UInt32? = nil - fileprivate var _particles03Um: UInt32? = nil - fileprivate var _particles05Um: UInt32? = nil - fileprivate var _particles10Um: UInt32? = nil - fileprivate var _particles25Um: UInt32? = nil - fileprivate var _particles50Um: UInt32? = nil - fileprivate var _particles100Um: UInt32? = nil - fileprivate var _co2: UInt32? = nil - fileprivate var _co2Temperature: Float? = nil - fileprivate var _co2Humidity: Float? = nil + fileprivate var _storage = _StorageClass.defaultInstance } /// @@ -1441,6 +1474,8 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding { 38: .same(proto: "MAX17261"), 39: .same(proto: "PCT2075"), 40: .same(proto: "ADS1X15"), + 41: .same(proto: "ADS1X15_ALT"), + 42: .same(proto: "SFA30"), ] } @@ -1892,103 +1927,195 @@ extension AirQualityMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 13: .same(proto: "co2"), 14: .standard(proto: "co2_temperature"), 15: .standard(proto: "co2_humidity"), + 16: .standard(proto: "form_formaldehyde"), + 17: .standard(proto: "form_humidity"), + 18: .standard(proto: "form_temperature"), ] + fileprivate class _StorageClass { + var _pm10Standard: UInt32? = nil + var _pm25Standard: UInt32? = nil + var _pm100Standard: UInt32? = nil + var _pm10Environmental: UInt32? = nil + var _pm25Environmental: UInt32? = nil + var _pm100Environmental: UInt32? = nil + var _particles03Um: UInt32? = nil + var _particles05Um: UInt32? = nil + var _particles10Um: UInt32? = nil + var _particles25Um: UInt32? = nil + var _particles50Um: UInt32? = nil + var _particles100Um: UInt32? = nil + var _co2: UInt32? = nil + var _co2Temperature: Float? = nil + var _co2Humidity: Float? = nil + var _formFormaldehyde: Float? = nil + var _formHumidity: Float? = nil + var _formTemperature: Float? = nil + + #if swift(>=5.10) + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + #else + static let defaultInstance = _StorageClass() + #endif + + private init() {} + + init(copying source: _StorageClass) { + _pm10Standard = source._pm10Standard + _pm25Standard = source._pm25Standard + _pm100Standard = source._pm100Standard + _pm10Environmental = source._pm10Environmental + _pm25Environmental = source._pm25Environmental + _pm100Environmental = source._pm100Environmental + _particles03Um = source._particles03Um + _particles05Um = source._particles05Um + _particles10Um = source._particles10Um + _particles25Um = source._particles25Um + _particles50Um = source._particles50Um + _particles100Um = source._particles100Um + _co2 = source._co2 + _co2Temperature = source._co2Temperature + _co2Humidity = source._co2Humidity + _formFormaldehyde = source._formFormaldehyde + _formHumidity = source._formHumidity + _formTemperature = source._formTemperature + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularUInt32Field(value: &self._pm10Standard) }() - case 2: try { try decoder.decodeSingularUInt32Field(value: &self._pm25Standard) }() - case 3: try { try decoder.decodeSingularUInt32Field(value: &self._pm100Standard) }() - case 4: try { try decoder.decodeSingularUInt32Field(value: &self._pm10Environmental) }() - case 5: try { try decoder.decodeSingularUInt32Field(value: &self._pm25Environmental) }() - case 6: try { try decoder.decodeSingularUInt32Field(value: &self._pm100Environmental) }() - case 7: try { try decoder.decodeSingularUInt32Field(value: &self._particles03Um) }() - case 8: try { try decoder.decodeSingularUInt32Field(value: &self._particles05Um) }() - case 9: try { try decoder.decodeSingularUInt32Field(value: &self._particles10Um) }() - case 10: try { try decoder.decodeSingularUInt32Field(value: &self._particles25Um) }() - case 11: try { try decoder.decodeSingularUInt32Field(value: &self._particles50Um) }() - case 12: try { try decoder.decodeSingularUInt32Field(value: &self._particles100Um) }() - case 13: try { try decoder.decodeSingularUInt32Field(value: &self._co2) }() - case 14: try { try decoder.decodeSingularFloatField(value: &self._co2Temperature) }() - case 15: try { try decoder.decodeSingularFloatField(value: &self._co2Humidity) }() - default: break + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &_storage._pm10Standard) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &_storage._pm25Standard) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &_storage._pm100Standard) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &_storage._pm10Environmental) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &_storage._pm25Environmental) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &_storage._pm100Environmental) }() + case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._particles03Um) }() + case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._particles05Um) }() + case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._particles10Um) }() + case 10: try { try decoder.decodeSingularUInt32Field(value: &_storage._particles25Um) }() + case 11: try { try decoder.decodeSingularUInt32Field(value: &_storage._particles50Um) }() + case 12: try { try decoder.decodeSingularUInt32Field(value: &_storage._particles100Um) }() + case 13: try { try decoder.decodeSingularUInt32Field(value: &_storage._co2) }() + case 14: try { try decoder.decodeSingularFloatField(value: &_storage._co2Temperature) }() + case 15: try { try decoder.decodeSingularFloatField(value: &_storage._co2Humidity) }() + case 16: try { try decoder.decodeSingularFloatField(value: &_storage._formFormaldehyde) }() + case 17: try { try decoder.decodeSingularFloatField(value: &_storage._formHumidity) }() + case 18: try { try decoder.decodeSingularFloatField(value: &_storage._formTemperature) }() + default: break + } } } } public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._pm10Standard { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) - } }() - try { if let v = self._pm25Standard { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) - } }() - try { if let v = self._pm100Standard { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) - } }() - try { if let v = self._pm10Environmental { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) - } }() - try { if let v = self._pm25Environmental { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) - } }() - try { if let v = self._pm100Environmental { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 6) - } }() - try { if let v = self._particles03Um { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 7) - } }() - try { if let v = self._particles05Um { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 8) - } }() - try { if let v = self._particles10Um { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9) - } }() - try { if let v = self._particles25Um { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 10) - } }() - try { if let v = self._particles50Um { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 11) - } }() - try { if let v = self._particles100Um { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 12) - } }() - try { if let v = self._co2 { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 13) - } }() - try { if let v = self._co2Temperature { - try visitor.visitSingularFloatField(value: v, fieldNumber: 14) - } }() - try { if let v = self._co2Humidity { - try visitor.visitSingularFloatField(value: v, fieldNumber: 15) - } }() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = _storage._pm10Standard { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._pm25Standard { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._pm100Standard { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } }() + try { if let v = _storage._pm10Environmental { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) + } }() + try { if let v = _storage._pm25Environmental { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) + } }() + try { if let v = _storage._pm100Environmental { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 6) + } }() + try { if let v = _storage._particles03Um { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 7) + } }() + try { if let v = _storage._particles05Um { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 8) + } }() + try { if let v = _storage._particles10Um { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9) + } }() + try { if let v = _storage._particles25Um { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 10) + } }() + try { if let v = _storage._particles50Um { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 11) + } }() + try { if let v = _storage._particles100Um { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 12) + } }() + try { if let v = _storage._co2 { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 13) + } }() + try { if let v = _storage._co2Temperature { + try visitor.visitSingularFloatField(value: v, fieldNumber: 14) + } }() + try { if let v = _storage._co2Humidity { + try visitor.visitSingularFloatField(value: v, fieldNumber: 15) + } }() + try { if let v = _storage._formFormaldehyde { + try visitor.visitSingularFloatField(value: v, fieldNumber: 16) + } }() + try { if let v = _storage._formHumidity { + try visitor.visitSingularFloatField(value: v, fieldNumber: 17) + } }() + try { if let v = _storage._formTemperature { + try visitor.visitSingularFloatField(value: v, fieldNumber: 18) + } }() + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: AirQualityMetrics, rhs: AirQualityMetrics) -> Bool { - if lhs._pm10Standard != rhs._pm10Standard {return false} - if lhs._pm25Standard != rhs._pm25Standard {return false} - if lhs._pm100Standard != rhs._pm100Standard {return false} - if lhs._pm10Environmental != rhs._pm10Environmental {return false} - if lhs._pm25Environmental != rhs._pm25Environmental {return false} - if lhs._pm100Environmental != rhs._pm100Environmental {return false} - if lhs._particles03Um != rhs._particles03Um {return false} - if lhs._particles05Um != rhs._particles05Um {return false} - if lhs._particles10Um != rhs._particles10Um {return false} - if lhs._particles25Um != rhs._particles25Um {return false} - if lhs._particles50Um != rhs._particles50Um {return false} - if lhs._particles100Um != rhs._particles100Um {return false} - if lhs._co2 != rhs._co2 {return false} - if lhs._co2Temperature != rhs._co2Temperature {return false} - if lhs._co2Humidity != rhs._co2Humidity {return false} + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._pm10Standard != rhs_storage._pm10Standard {return false} + if _storage._pm25Standard != rhs_storage._pm25Standard {return false} + if _storage._pm100Standard != rhs_storage._pm100Standard {return false} + if _storage._pm10Environmental != rhs_storage._pm10Environmental {return false} + if _storage._pm25Environmental != rhs_storage._pm25Environmental {return false} + if _storage._pm100Environmental != rhs_storage._pm100Environmental {return false} + if _storage._particles03Um != rhs_storage._particles03Um {return false} + if _storage._particles05Um != rhs_storage._particles05Um {return false} + if _storage._particles10Um != rhs_storage._particles10Um {return false} + if _storage._particles25Um != rhs_storage._particles25Um {return false} + if _storage._particles50Um != rhs_storage._particles50Um {return false} + if _storage._particles100Um != rhs_storage._particles100Um {return false} + if _storage._co2 != rhs_storage._co2 {return false} + if _storage._co2Temperature != rhs_storage._co2Temperature {return false} + if _storage._co2Humidity != rhs_storage._co2Humidity {return false} + if _storage._formFormaldehyde != rhs_storage._formFormaldehyde {return false} + if _storage._formHumidity != rhs_storage._formHumidity {return false} + if _storage._formTemperature != rhs_storage._formTemperature {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true }