Merge pull request #1322 from meshtastic/2.6.15

2.6.15 Working Changes
This commit is contained in:
Garth Vander Houwen 2025-07-21 16:51:53 -07:00 committed by GitHub
commit 8d77da0868
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 961 additions and 784 deletions

View file

@ -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" : {

View file

@ -568,6 +568,7 @@
DDDE5A0F29AFE69700490C6C /* MeshActivityAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshActivityAttributes.swift; sourceTree = "<group>"; };
DDDE5A1229AFEAB900490C6C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = "<group>"; };
DDDF34392E2CB8E600356DC3 /* MeshtasticDataModelV 54.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 54.xcdatamodel"; sourceTree = "<group>"; };
DDDFE73E2D0D48FF0044463C /* IgnoreNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IgnoreNodeButton.swift; sourceTree = "<group>"; };
DDDFE7402D0D4A070044463C /* MeshtasticDataModelV 47.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 47.xcdatamodel"; sourceTree = "<group>"; };
DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = "<group>"; };
@ -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 = "<group>";

View file

@ -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:

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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 })

View file

@ -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<CLAuthorizationStatus, Never>?
// 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

View file

@ -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) {

View file

@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>MeshtasticDataModelV 53.xcdatamodel</string>
<string>MeshtasticDataModelV 54.xcdatamodel</string>
</dict>
</plist>

View file

@ -0,0 +1,505 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23788.4" systemVersion="24D81" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AmbientLightingConfigEntity" representedClassName="AmbientLightingConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="blue" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="current" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="green" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ledState" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="red" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="ambientLightingConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="ambientLightingConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="BluetoothConfigEntity" representedClassName="BluetoothConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceLoggingEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPin" optional="YES" attributeType="Integer 32" defaultValueString="123456" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="bluetoothConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="bluetoothConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="CannedMessageConfigEntity" representedClassName="CannedMessageConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventCcw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventCw" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerEventPress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinA" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinB" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="inputbrokerPinPress" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="messages" optional="YES" attributeType="String" minValueString="0" maxValueString="198"/>
<attribute name="rotary1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="updown1Enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="cannedMessagesConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="cannedMessageConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ChannelEntity" representedClassName="ChannelEntity" syncable="YES" codeGenerationType="class">
<attribute name="downlinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="index" attributeType="Integer 32" minValueString="0" maxValueString="13" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="positionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
<attribute name="psk" optional="YES" attributeType="Binary"/>
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="uplinkEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="myInfoChannel" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="channels" inverseEntity="MyInfoEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="index"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="DetectionSensorConfigEntity" representedClassName="DetectionSensorConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="minimumBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="monitorPin" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="sendBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="stateBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="triggerType" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="usePullup" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="detectionSensorConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="detectionSensorConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceConfigEntity" representedClassName="DeviceConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="buttonGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="buzzerGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="debugLogEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="disableTripleClick" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="doubleTapAsButtonPress" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="isManaged" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="ledHeartbeatEnabled" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="nodeInfoBroadcastSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rebroadcastMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="serialEnabled" optional="YES" attributeType="Boolean" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tripleClickAsAdHocPing" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="tzdef" optional="YES" attributeType="String"/>
<relationship name="deviceConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="deviceConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DeviceMetadataEntity" representedClassName="DeviceMetadataEntity" syncable="YES" codeGenerationType="class">
<attribute name="canShutdown" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="deviceStateVersion" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="excludedModules" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="firmwareVersion" optional="YES" attributeType="String"/>
<attribute name="hasBluetooth" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasEthernet" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hasWifi" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="hwModel" optional="YES" attributeType="String"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="role" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="metadataNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="metadata" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="DisplayConfigEntity" representedClassName="DisplayConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="compassNorthTop" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="displayMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="flipScreen" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="headingBold" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="oledType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenCarouselInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="screenOnSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="units" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="use12HClock" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="wakeOnTapOrMotion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="displayConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="displayConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="ExternalNotificationConfigEntity" representedClassName="ExternalNotificationConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="active" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBell" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertBellVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessage" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="alertMessageVibra" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="nagTimeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="output" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputBuzzer" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputMilliseconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="outputVibra" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="useI2SAsBuzzer" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="usePWM" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="externalNotificationConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="externalNotificationConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="LocationEntity" representedClassName="LocationEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="routeLocation" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RouteEntity" inverseName="locations" inverseEntity="RouteEntity"/>
</entity>
<entity name="LoRaConfigEntity" representedClassName="LoRaConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="bandwidth" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="codingRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="frequencyOffset" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hopLimit" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ignoreMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="modemPreset" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="okToMqtt" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="overrideDutyCycle" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="overrideFrequency" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="regionCode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="spreadFactor" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sx126xRxBoostedGain" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="txPower" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="usePreset" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<relationship name="loRaConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="loRaConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MessageEntity" representedClassName="MessageEntity" syncable="YES" codeGenerationType="class">
<attribute name="ackError" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ackSNR" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminDescription" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="messagePayload" optional="YES" attributeType="String" defaultValueString=""/>
<attribute name="messagePayloadMarkdown" optional="YES" attributeType="String"/>
<attribute name="messageTimestamp" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="portNum" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
<attribute name="read" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="realACK" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="receivedACK" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="replyID" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="fromUser" optional="YES" maxCount="1" deletionRule="Nullify" ordered="YES" destinationEntity="UserEntity" inverseName="sentMessages" inverseEntity="UserEntity"/>
<relationship name="toUser" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="receivedMessages" inverseEntity="UserEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="messageId"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="MQTTConfigEntity" representedClassName="MQTTConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="address" optional="YES" attributeType="String"/>
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="encryptionEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="jsonEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="mapPositionPrecision" optional="YES" attributeType="Integer 32" defaultValueString="13" usesScalarValueType="YES"/>
<attribute name="mapPublishIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="mapReportingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="mapReportingShouldReportLocation" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="password" optional="YES" attributeType="String" maxValueString="30"/>
<attribute name="proxyToClientEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="root" optional="YES" attributeType="String" defaultValueString="msh"/>
<attribute name="tlsEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="username" optional="YES" attributeType="String" maxValueString="30"/>
<relationship name="mqttConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="mqttConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="MyInfoEntity" representedClassName="MyInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="deviceId" optional="YES" attributeType="Binary"/>
<attribute name="minAppVersion" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="myNodeNum" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="peripheralId" optional="YES" attributeType="String"/>
<attribute name="rebootCount" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="registered" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="channels" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="ChannelEntity" inverseName="myInfoChannel" inverseEntity="ChannelEntity"/>
<relationship name="myInfoNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="myInfo" inverseEntity="NodeInfoEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="myNodeNum"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="NetworkConfigEntity" representedClassName="NetworkConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="dns" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabledProtocols" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ethEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="gateway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ip" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ntpServer" optional="YES" attributeType="String"/>
<attribute name="subnet" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifiEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="wifiMode" optional="YES" attributeType="Integer 32" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="wifiPsk" optional="YES" attributeType="String" minValueString="0" maxValueString="60"/>
<attribute name="wifiSsid" optional="YES" attributeType="String" minValueString="0" maxValueString="30"/>
<relationship name="networkConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="networkConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="NodeInfoEntity" representedClassName="NodeInfoEntity" syncable="YES" codeGenerationType="class">
<attribute name="bleName" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="favorite" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="firstHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="hopsAway" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ignored" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastHeard" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="peripheralId" optional="YES" attributeType="String"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sessionExpiration" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="sessionPasskey" optional="YES" attributeType="Binary"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="viaMqtt" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="ambientLightingConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="AmbientLightingConfigEntity" inverseName="ambientLightingConfigNode" inverseEntity="AmbientLightingConfigEntity"/>
<relationship name="bluetoothConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="BluetoothConfigEntity" inverseName="bluetoothConfigNode" inverseEntity="BluetoothConfigEntity"/>
<relationship name="cannedMessageConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="CannedMessageConfigEntity" inverseName="cannedMessagesConfigNode" inverseEntity="CannedMessageConfigEntity"/>
<relationship name="detectionSensorConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DetectionSensorConfigEntity" inverseName="detectionSensorConfigNode" inverseEntity="DetectionSensorConfigEntity"/>
<relationship name="deviceConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DeviceConfigEntity" inverseName="deviceConfigNode" inverseEntity="DeviceConfigEntity"/>
<relationship name="displayConfig" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="DisplayConfigEntity" inverseName="displayConfigNode" inverseEntity="DisplayConfigEntity"/>
<relationship name="externalNotificationConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ExternalNotificationConfigEntity" inverseName="externalNotificationConfigNode" inverseEntity="ExternalNotificationConfigEntity"/>
<relationship name="loRaConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="LoRaConfigEntity" inverseName="loRaConfigNode" inverseEntity="LoRaConfigEntity"/>
<relationship name="metadata" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="DeviceMetadataEntity" inverseName="metadataNode" inverseEntity="DeviceMetadataEntity"/>
<relationship name="mqttConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MQTTConfigEntity" inverseName="mqttConfigNode" inverseEntity="MQTTConfigEntity"/>
<relationship name="myInfo" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MyInfoEntity" inverseName="myInfoNode" inverseEntity="MyInfoEntity"/>
<relationship name="networkConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NetworkConfigEntity" inverseName="networkConfigNode" inverseEntity="NetworkConfigEntity"/>
<relationship name="pax" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PaxCounterEntity" inverseName="paxNode" inverseEntity="PaxCounterEntity"/>
<relationship name="paxCounterConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PaxCounterConfigEntity" inverseName="paxCounterConfigNode" inverseEntity="PaxCounterConfigEntity"/>
<relationship name="positionConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PositionConfigEntity" inverseName="positionConfigNode" inverseEntity="PositionConfigEntity"/>
<relationship name="positions" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PositionEntity" inverseName="nodePosition" inverseEntity="PositionEntity"/>
<relationship name="powerConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PowerConfigEntity" inverseName="powerConfigNode" inverseEntity="PowerConfigEntity"/>
<relationship name="rangeTestConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RangeTestConfigEntity" inverseName="rangeTestConfigNode" inverseEntity="RangeTestConfigEntity"/>
<relationship name="rtttlConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="RTTTLConfigEntity" inverseName="rtttlConfigNode" inverseEntity="RTTTLConfigEntity"/>
<relationship name="securityConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SecurityConfigEntity" inverseName="securityConfigNode" inverseEntity="SecurityConfigEntity"/>
<relationship name="serialConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="SerialConfigEntity" inverseName="serialConfigNode" inverseEntity="SerialConfigEntity"/>
<relationship name="storeForwardConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreForwardConfigEntity" inverseName="storeForwardConfigNode" inverseEntity="StoreForwardConfigEntity"/>
<relationship name="telemetries" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TelemetryEntity" inverseName="nodeTelemetry" inverseEntity="TelemetryEntity"/>
<relationship name="telemetryConfig" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TelemetryConfigEntity" inverseName="telemetryConfigNode" inverseEntity="TelemetryConfigEntity"/>
<relationship name="traceRoutes" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="TraceRouteEntity" inverseName="node" inverseEntity="TraceRouteEntity"/>
<relationship name="user" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="UserEntity" inverseName="userNode" inverseEntity="UserEntity"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="num"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="PaxCounterConfigEntity" representedClassName="PaxCounterConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="bleThreshold" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="updateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifiThreshold" optional="YES" attributeType="Integer 32" defaultValueString="-80" usesScalarValueType="YES"/>
<relationship name="paxCounterConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="paxCounterConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PaxCounterEntity" representedClassName="PaxCounterEntity" syncable="YES" codeGenerationType="class">
<attribute name="ble" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="uptime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="wifi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="paxNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="pax" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PositionConfigEntity" representedClassName="PositionConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="broadcastSmartMinimumDistance" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="broadcastSmartMinimumIntervalSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="deviceGpsEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="fixedPosition" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="gpsAttemptTime" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsEnGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsMode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="gpsUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionBroadcastSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="positionFlags" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rxGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="smartPositionEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="txGpio" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="positionConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positionConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PositionEntity" representedClassName="PositionEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="heading" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="latest" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="precisionBits" optional="YES" attributeType="Integer 32" defaultValueString="32" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="satsInView" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="seqNo" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="speed" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="nodePosition" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="positions" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="PowerConfigEntity" representedClassName="PowerConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="adcMultiplierOverride" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="deviceBatteryInaAddress" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isPowerSaving" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="lsSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="minWakeSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="onBatteryShutdownAfterSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="waitBluetoothSecs" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="powerConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="powerConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RangeTestConfigEntity" representedClassName="RangeTestConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="save" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="sender" optional="YES" attributeType="Integer 32" usesScalarValueType="YES"/>
<relationship name="rangeTestConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rangeTestConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="RouteEntity" representedClassName="RouteEntity" syncable="YES" codeGenerationType="class">
<attribute name="color" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="distance" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="elevationGain" optional="YES" attributeType="Double" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="endDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="id" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="notes" optional="YES" attributeType="String"/>
<relationship name="locations" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="LocationEntity" inverseName="routeLocation" inverseEntity="LocationEntity"/>
</entity>
<entity name="RTTTLConfigEntity" representedClassName="RTTTLConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="ringtone" optional="YES" attributeType="String" maxValueString="228" defaultValueString=""/>
<relationship name="rtttlConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="rtttlConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="SecurityConfigEntity" representedClassName="SecurityConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="adminChannelEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminKey" optional="YES" attributeType="Binary"/>
<attribute name="adminKey2" optional="YES" attributeType="Binary"/>
<attribute name="adminKey3" optional="YES" attributeType="Binary"/>
<attribute name="bluetoothLoggingEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="debugLogApiEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="isManaged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="privateKey" optional="YES" attributeType="Binary"/>
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
<attribute name="serialEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<relationship name="securityConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="securityConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="SerialConfigEntity" representedClassName="SerialConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="baudRate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="echo" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="enabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="mode" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="overrideConsoleSerialPort" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="rxd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="timeout" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="txd" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="serialConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="serialConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="StoreForwardConfigEntity" representedClassName="StoreForwardConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="enabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="heartbeat" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="historyReturnMax" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="historyReturnWindow" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isRouter" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="lastHeartbeat" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="lastRequest" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="records" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="storeForwardConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="storeForwardConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryConfigEntity" representedClassName="TelemetryConfigEntity" syncable="YES" codeGenerationType="class">
<attribute name="deviceUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="environmentDisplayFahrenheit" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentMeasurementEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="environmentUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="powerMeasurementEnabled" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="powerScreenEnabled" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="powerUpdateInterval" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="telemetryConfigNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetryConfig" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TelemetryEntity" representedClassName="TelemetryEntity" syncable="YES">
<attribute name="airUtilTx" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="barometricPressure" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="batteryLevel" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelUtilization" optional="YES" attributeType="Float" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="current" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="gasResistance" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="iaq" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="irLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="lux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="metricsType" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numOnlineNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsRx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsRxBad" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numPacketsTx" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numRxDupe" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTotalNodes" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTxRelay" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numTxRelayCanceled" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="powerCh1Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh1Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh2Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh2Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh3Current" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="powerCh3Voltage" optional="YES" attributeType="Float" usesScalarValueType="YES"/>
<attribute name="radiation" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rainfall1H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rainfall24H" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="relativeHumidity" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="rssi" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="soilMoisture" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="soilTemperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="temperature" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="uptimeSeconds" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="uvLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="voltage" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="weight" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="whiteLux" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windDirection" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="windGust" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windLull" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="windSpeed" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<relationship name="nodeTelemetry" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="telemetries" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TraceRouteEntity" representedClassName="TraceRouteEntity" syncable="YES" codeGenerationType="class">
<attribute name="hasPositions" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hopsBack" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hopsTowards" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="response" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="routeBackText" optional="YES" attributeType="String"/>
<attribute name="routeText" optional="YES" attributeType="String"/>
<attribute name="sent" optional="YES" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="hops" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="TraceRouteHopEntity" inverseName="traceRoute" inverseEntity="TraceRouteHopEntity"/>
<relationship name="node" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="traceRoutes" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="TraceRouteHopEntity" representedClassName="TraceRouteHopEntity" syncable="YES" codeGenerationType="class">
<attribute name="altitude" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="back" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="latitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="longitudeI" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="num" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="snr" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="time" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="traceRoute" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TraceRouteEntity" inverseName="hops" inverseEntity="TraceRouteEntity"/>
</entity>
<entity name="UserEntity" representedClassName="UserEntity" syncable="YES" codeGenerationType="class">
<attribute name="hwDisplayName" optional="YES" attributeType="String"/>
<attribute name="hwModel" attributeType="String"/>
<attribute name="hwModelId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="isLicensed" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="keyMatch" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="lastMessage" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="longName" attributeType="String"/>
<attribute name="mute" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="newPublicKey" optional="YES" attributeType="Binary"/>
<attribute name="num" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numString" optional="YES" attributeType="String"/>
<attribute name="pkiEncrypted" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="publicKey" optional="YES" attributeType="Binary"/>
<attribute name="role" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="shortName" attributeType="String"/>
<attribute name="unmessagable" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="userId" attributeType="String"/>
<relationship name="receivedMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="toUser" inverseEntity="MessageEntity"/>
<relationship name="sentMessages" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="MessageEntity" inverseName="fromUser" inverseEntity="MessageEntity"/>
<relationship name="userNode" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="NodeInfoEntity" inverseName="user" inverseEntity="NodeInfoEntity"/>
</entity>
<entity name="WaypointEntity" representedClassName="WaypointEntity" syncable="YES" codeGenerationType="class">
<attribute name="created" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="expire" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="icon" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="latitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="locked" attributeType="Integer 64" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="longDescription" optional="YES" attributeType="String" maxValueString="100"/>
<attribute name="longitudeI" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String" minValueString="1" maxValueString="30"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="id"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
</model>

View file

@ -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

View file

@ -72,7 +72,7 @@ struct ContentView: View {
isShowingDeviceOnboardingFlow = true
}
}
.onChange(of: UserDefaults.showDeviceOnboarding) { newValue in
.onChange(of: UserDefaults.showDeviceOnboarding) {_, newValue in
isShowingDeviceOnboardingFlow = newValue
}
}

View file

@ -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
}
}

View file

@ -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
///

View file

@ -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"),
]
}

View file

@ -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<D: SwiftProtobuf.Decoder>(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<V: SwiftProtobuf.Visitor>(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
}