diff --git a/Localizable.xcstrings b/Localizable.xcstrings index a3bd1133..811fbdd2 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -334,9 +334,6 @@ }, "Ack Time: %@" : { - }, - "Acknowledged" : { - }, "Acknowledged by another node" : { @@ -481,6 +478,12 @@ } } } + }, + "Admin & Direct Message Keys" : { + + }, + "Admin Key" : { + }, "admin.log" : { "extractionState" : "manual", @@ -703,7 +706,10 @@ "All" : { }, - "All device and app data will be deleted. You will also need to forget your devices under Settings > Bluetooth." : { + "All device and app data will be deleted." : { + + }, + "Allow incoming device control over the insecure legacy admin channel." : { }, "Allow Position Requests" : { @@ -1843,6 +1849,9 @@ } } } + }, + "Bluetooth Logs" : { + }, "bluetooth.config" : { "localizations" : { @@ -4804,9 +4813,6 @@ } } } - }, - "Contacts" : { - }, "contacts %@" : { "extractionState" : "migrated", @@ -5346,6 +5352,9 @@ } } } + }, + "Developer" : { + }, "Developers" : { @@ -5410,6 +5419,9 @@ }, "Device GPS" : { + }, + "Device is managed by a mesh administrator." : { + }, "Device Logging Enabled" : { @@ -6309,6 +6321,15 @@ }, "Direct" : { + }, + "Direct Message Help" : { + + }, + "Direct messages are using the new public key infrastructure for encryption. Reguires firmware version 2.5 or greater." : { + + }, + "Direct messages are using the shared key for the channel." : { + }, "direct.messages" : { "localizations" : { @@ -6691,9 +6712,6 @@ }, "Drag & Drop is the recommended way to update firmware for NRF devices. If your iPhone or iPad is USB-C it will work with your regular USB-C charging cable, for lightning devices you need the Apple Lightning to USB camera adaptor." : { - }, - "Each node is an available contact. Contacts with recent messages or marked as favorites show up at the top of the list. Select a contact to send or view messages. Long press to favorite or mute the contact or delete the conversation." : { - }, "echo" : { "localizations" : { @@ -6966,6 +6984,9 @@ } } } + }, + "Encrypted" : { + }, "Encryption Enabled" : { @@ -7208,7 +7229,10 @@ }, "Fetch the latest position of a cetain node" : { - + + }, + "Favorites and nodes with recent messages show up at the top of the contact list." : { + }, "Fifteen Minutes" : { @@ -8276,11 +8300,14 @@ "Hops Away" : { }, - "Hops Away %d) dB" : { + "Hops Away %d" : { }, "Hops Away:" : { + }, + "Hops Away: %d" : { + }, "Hour" : { @@ -10867,6 +10894,9 @@ }, "LED State" : { + }, + "Legacy Administration" : { + }, "Licensed Operator" : { @@ -11275,6 +11305,9 @@ }, "Long Name: %@" : { + }, + "Long press to favorite or mute the contact or delete a conversation." : { + }, "Longitude" : { @@ -14532,7 +14565,9 @@ }, "Message content exceeds 228 bytes." : { - + }, + "Message Status Options" : { + }, "message.details" : { "localizations" : { @@ -16021,6 +16056,9 @@ }, "Other data sources" : { + }, + "Output live debug logging over serial." : { + }, "Output pin buzzer GPIO " : { @@ -16671,9 +16709,21 @@ }, "Primary GPIO" : { + }, + "Private Key" : { + }, "Project information" : { + }, + "Public Key" : { + + }, + "Public Key Encryption" : { + + }, + "Public Key Mismatch" : { + }, "PWD" : { @@ -17252,7 +17302,10 @@ "Remote administration for: %@" : { }, - "Remote: %@" : { + "Remote Legacy Admin: %@" : { + + }, + "Remote PKI Admin: %@" : { }, "Remove" : { @@ -17322,7 +17375,10 @@ } } }, - "Request Admin: %@" : { + "Request Legacy Admin: %@" : { + + }, + "Request PKI Admin: %@" : { }, "Requires that there be an accelerometer on your device." : { @@ -18452,6 +18508,28 @@ } } }, + "routing.pkifailed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Encrypted Send Failed" + } + } + } + }, + "routing.pkiunknownpubkey" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown Public Key" + } + } + } + }, "routing.timeout" : { "extractionState" : "migrated", "localizations" : { @@ -18846,6 +18924,12 @@ }, "Secondary" : { + }, + "Security" : { + + }, + "Security Config" : { + }, "Select a channel" : { @@ -19070,6 +19154,9 @@ }, "Sensor Options" : { + }, + "Sent out to other nodes on the mesh to allow them to compute a shared secret key." : { + }, "Sequence number" : { @@ -19137,6 +19224,12 @@ }, "Serial Console" : { + }, + "Serial Console over the Stream API." : { + + }, + "Serial Debug Logs" : { + }, "serial.config" : { "localizations" : { @@ -19741,6 +19834,9 @@ } } } + }, + "Shared Key" : { + }, "Short Name" : { @@ -20970,6 +21066,12 @@ }, "The minimum distance change in meters to be considered for a smart position broadcast." : { + }, + "The public key authorized to send admin messages to this node." : { + + }, + "The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action." : { + }, "The region where you will be using your radios." : { @@ -22181,6 +22283,9 @@ }, "Use PWM Buzzer" : { + }, + "Used to create a shared key with a remote device." : { + }, "user" : { "localizations" : { @@ -22348,6 +22453,12 @@ }, "Via Mqtt" : { + }, + "View and export position-redacted device logs over Bluetooth" : { + + }, + "View Logs" : { + }, "voltage" : { "localizations" : { @@ -22485,6 +22596,9 @@ }, "Website" : { + }, + "What does the lock mean?" : { + }, "What is Meshtastic?" : { @@ -22503,6 +22617,12 @@ }, "WIND" : { + }, + "Wind Direction" : { + + }, + "Wind Speed" : { + }, "x" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 49a91c7d..6f7c7e58 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ DD1B8F402B35E2F10022AABC /* GPSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1B8F3F2B35E2F10022AABC /* GPSStatus.swift */; }; DD1BD0EB2C601795008C0C70 /* CLLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0EA2C601795008C0C70 /* CLLocation.swift */; }; DD1BD0EE2C603C91008C0C70 /* CustomFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */; }; + DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */; }; DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */; }; DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2160AE28C5552500C17253 /* MQTTConfig.swift */; }; DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; @@ -95,6 +96,11 @@ DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; + DD6F65722C6AB8EC0053C113 /* SecureInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65712C6AB8EC0053C113 /* SecureInput.swift */; }; + DD6F65742C6CB80A0053C113 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65732C6CB80A0053C113 /* View.swift */; }; + DD6F65762C6EA5490053C113 /* AckErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65752C6EA5490053C113 /* AckErrors.swift */; }; + DD6F65792C6EADE60053C113 /* DirectMessagesHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F65782C6EADE60053C113 /* DirectMessagesHelp.swift */; }; + DD6F657B2C6EC2900053C113 /* LockLegend.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F657A2C6EC2900053C113 /* LockLegend.swift */; }; DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; }; DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; }; DD77093B2AA1ABB8007A8BF0 /* BluetoothTips.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD77093A2AA1ABB8007A8BF0 /* BluetoothTips.swift */; }; @@ -150,7 +156,6 @@ DDB75A1A2A05EB67006ED576 /* alpha.png in Resources */ = {isa = PBXBuildFile; fileRef = DDB75A192A05EB67006ED576 /* alpha.png */; }; DDB75A1E2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */; }; DDB75A212A12B954006ED576 /* LoRaSignalStrength.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */; }; - DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */; }; DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8F40F2A9EE5B400230ECE /* Messages.swift */; }; DDB8F4122A9EE5DD00230ECE /* UserList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8F4112A9EE5DD00230ECE /* UserList.swift */; }; DDB8F4142A9EE5F000230ECE /* ChannelList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDB8F4132A9EE5F000230ECE /* ChannelList.swift */; }; @@ -300,6 +305,7 @@ DD1BD0EA2C601795008C0C70 /* CLLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocation.swift; sourceTree = ""; }; DD1BD0ED2C603C91008C0C70 /* CustomFormatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFormatters.swift; sourceTree = ""; }; DD1BD0F12C61D3AD008C0C70 /* MeshtasticDataModelV 42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 42.xcdatamodel"; sourceTree = ""; }; + DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityConfig.swift; sourceTree = ""; }; DD1BF2F82776FE2E008C8D2F /* UserMessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageList.swift; sourceTree = ""; }; DD2160AE28C5552500C17253 /* MQTTConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MQTTConfig.swift; sourceTree = ""; }; DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; @@ -351,6 +357,11 @@ DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; DD68BAE72C417A74004C01A0 /* MeshtasticDataModelV 40.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 40.xcdatamodel"; sourceTree = ""; }; + DD6F65712C6AB8EC0053C113 /* SecureInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureInput.swift; sourceTree = ""; }; + DD6F65732C6CB80A0053C113 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; + DD6F65752C6EA5490053C113 /* AckErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AckErrors.swift; sourceTree = ""; }; + DD6F65782C6EADE60053C113 /* DirectMessagesHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesHelp.swift; sourceTree = ""; }; + DD6F657A2C6EC2900053C113 /* LockLegend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockLegend.swift; sourceTree = ""; }; DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; DD77093A2AA1ABB8007A8BF0 /* BluetoothTips.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTips.swift; sourceTree = ""; }; @@ -414,7 +425,6 @@ DDB75A1D2A0B0CD0006ED576 /* LoRaSignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrengthIndicator.swift; sourceTree = ""; }; DDB75A1F2A10766D006ED576 /* MeshtasticDataModelV13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV13.xcdatamodel; sourceTree = ""; }; DDB75A202A12B954006ED576 /* LoRaSignalStrength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaSignalStrength.swift; sourceTree = ""; }; - DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryLevelCompact.swift; sourceTree = ""; }; DDB8F40F2A9EE5B400230ECE /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; DDB8F4112A9EE5DD00230ECE /* UserList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserList.swift; sourceTree = ""; }; DDB8F4132A9EE5F000230ECE /* ChannelList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelList.swift; sourceTree = ""; }; @@ -699,6 +709,7 @@ DD2553582855B52700E55709 /* PositionConfig.swift */, DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */, D93068DA2B81C85E0066FBC8 /* PowerConfig.swift */, + DD1BD0F22C63C65E008C0C70 /* SecurityConfig.swift */, DD61937B2863877A00E59241 /* Module */, ); path = Config; @@ -722,6 +733,16 @@ path = Module; sourceTree = ""; }; + DD6F65772C6EAB860053C113 /* Help */ = { + isa = PBXGroup; + children = ( + DD6F65752C6EA5490053C113 /* AckErrors.swift */, + DD6F65782C6EADE60053C113 /* DirectMessagesHelp.swift */, + DD6F657A2C6EC2900053C113 /* LockLegend.swift */, + ); + path = Help; + sourceTree = ""; + }; DD7709392AA1ABA1007A8BF0 /* Tips */ = { isa = PBXGroup; children = ( @@ -915,9 +936,9 @@ DDC2E18D26CE25CB0042C5E4 /* Helpers */ = { isa = PBXGroup; children = ( + DD6F65772C6EAB860053C113 /* Help */, DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */, DD3CC24B2C498D6C001BD3A2 /* BatteryCompact.swift */, - DDB75A222A13CDA9006ED576 /* BatteryLevelCompact.swift */, DD457187293C7E63000C49FB /* BLESignalStrengthIndicator.swift */, DD47E3D526F17ED900029299 /* CircleText.swift */, DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, @@ -929,6 +950,7 @@ DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */, DDF45C332BC1A48E005ED5F2 /* MQTTIcon.swift */, DD5E523D298F5A7D00D21B61 /* Weather */, + DD6F65712C6AB8EC0053C113 /* SecureInput.swift */, ); path = Helpers; sourceTree = ""; @@ -1015,6 +1037,7 @@ DDD5BB172C2F9C36007E03CA /* OSLogEntryLog.swift */, DDF45C362BC46A5A005ED5F2 /* TimeZone.swift */, DDD5BB0C2C285F00007E03CA /* Logger.swift */, + DD6F65732C6CB80A0053C113 /* View.swift */, ); path = Extensions; sourceTree = ""; @@ -1296,19 +1319,21 @@ DDB8F4102A9EE5B400230ECE /* Messages.swift in Sources */, DDDB26482AACD6D1003AFCB7 /* NodeMapMapkit.swift in Sources */, DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */, + DD1BD0F32C63C65E008C0C70 /* SecurityConfig.swift in Sources */, DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */, + DD6F65722C6AB8EC0053C113 /* SecureInput.swift in Sources */, DD13AA492AB73BF400BA0C98 /* PositionPopover.swift in Sources */, 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */, DDDB444229F8A88700EE2349 /* Double.swift in Sources */, DDF45C342BC1A48E005ED5F2 /* MQTTIcon.swift in Sources */, DDA9515A2BC6624100CEA535 /* TelemetryWeather.swift in Sources */, - DDB75A232A13CDA9006ED576 /* BatteryLevelCompact.swift in Sources */, DDB75A162A0594AD006ED576 /* TileOverlay.swift in Sources */, DD1BD0EB2C601795008C0C70 /* CLLocation.swift in Sources */, DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */, DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */, DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, DD3619152B1EF9F900C41C8C /* LocationsHandler.swift in Sources */, + DD6F65792C6EADE60053C113 /* DirectMessagesHelp.swift in Sources */, 25F5D5C02C3F6DA6008036E3 /* Router.swift in Sources */, DDDB444A29F8AA3A00EE2349 /* CLLocationCoordinate2D.swift in Sources */, 25C49D902C471AEA0024FBD1 /* Constants.swift in Sources */, @@ -1368,6 +1393,7 @@ 251926852C3BA97800249DF5 /* FavoriteNodeButton.swift in Sources */, D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */, DD86D4112881D16900BAEB7A /* WriteCsvFile.swift in Sources */, + DD6F65762C6EA5490053C113 /* AckErrors.swift in Sources */, DDDB445029F8AC9C00EE2349 /* UIImage.swift in Sources */, DD86D40F2881BE4C00BAEB7A /* CsvDocument.swift in Sources */, DDB75A142A0593E2006ED576 /* OfflineTileManager.swift in Sources */, @@ -1388,6 +1414,7 @@ DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */, DDDCD5702BB26F5C00BE6B60 /* NodeListFilter.swift in Sources */, + DD6F65742C6CB80A0053C113 /* View.swift in Sources */, DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */, @@ -1396,6 +1423,7 @@ DD15E4F52B8BFC8E00654F61 /* PaxCounterLog.swift in Sources */, 25F5D5C22C3F6E4B008036E3 /* AppState.swift in Sources */, DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, + DD6F657B2C6EC2900053C113 /* LockLegend.swift in Sources */, DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */, DDAB580D2B0DAA9E00147258 /* Routes.swift in Sources */, BCB613832C672A2600485544 /* MessageChannelIntent.swift in Sources */, @@ -1641,7 +1669,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.4.2; + MARKETING_VERSION = 2.5.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1676,7 +1704,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.4.2; + MARKETING_VERSION = 2.5.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1708,7 +1736,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.2; + MARKETING_VERSION = 2.5.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1741,7 +1769,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.4.2; + MARKETING_VERSION = 2.5.1; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Enums/LoraConfigEnums.swift b/Meshtastic/Enums/LoraConfigEnums.swift index 7da6268e..b0dd9966 100644 --- a/Meshtastic/Enums/LoraConfigEnums.swift +++ b/Meshtastic/Enums/LoraConfigEnums.swift @@ -210,6 +210,7 @@ enum ModemPresets: Int, CaseIterable, Identifiable { case medFast = 4 case shortSlow = 5 case shortFast = 6 + case shortTurbo = 8 var id: Int { self.rawValue } var description: String { @@ -230,6 +231,8 @@ enum ModemPresets: Int, CaseIterable, Identifiable { return "Short Range - Slow" case .shortFast: return "Short Range - Fast" + case .shortTurbo: + return "Short Range - Turbo" } } var name: String { @@ -250,6 +253,8 @@ enum ModemPresets: Int, CaseIterable, Identifiable { return "ShortSlow" case .shortFast: return "ShortFast" + case .shortTurbo: + return "ShortTurbo" } } func snrLimit() -> Float { @@ -270,6 +275,8 @@ enum ModemPresets: Int, CaseIterable, Identifiable { return -10 case .shortFast: return -7.5 + case .shortTurbo: + return -7.5 } } func protoEnumValue() -> Config.LoRaConfig.ModemPreset { @@ -290,6 +297,8 @@ enum ModemPresets: Int, CaseIterable, Identifiable { return Config.LoRaConfig.ModemPreset.shortSlow case .shortFast: return Config.LoRaConfig.ModemPreset.shortFast + case .shortTurbo: + return Config.LoRaConfig.ModemPreset.shortTurbo } } } diff --git a/Meshtastic/Enums/RoutingError.swift b/Meshtastic/Enums/RoutingError.swift index 0773265b..108fedd2 100644 --- a/Meshtastic/Enums/RoutingError.swift +++ b/Meshtastic/Enums/RoutingError.swift @@ -5,6 +5,7 @@ // Copyright(c) Garth Vander Houwen 8/4/22. // import Foundation +import SwiftUI import MeshtasticProtobufs enum RoutingError: Int, CaseIterable, Identifiable { @@ -21,6 +22,8 @@ enum RoutingError: Int, CaseIterable, Identifiable { case dutyCycleLimit = 9 case badRequest = 32 case notAuthorized = 33 + case pkiFailed = 34 + case pkiUnknownPubkey = 35 var id: Int { self.rawValue } var display: String { @@ -50,6 +53,51 @@ enum RoutingError: Int, CaseIterable, Identifiable { return "routing.badRequest".localized case .notAuthorized: return "routing.notauthorized".localized + case .pkiFailed: + return "routing.pkifailed".localized + case .pkiUnknownPubkey: + return "routing.pkiunknownpubkey".localized + } + } + var color: Color { + if self == .none { + return Color.secondary + } else if self.canRetry { + return Color.orange + } else { + return Color.red + } + } + var canRetry: Bool { + switch self { + case .none: + return false + case .noRoute: + return true + case .gotNak: + return true + case .timeout: + return true + case .noInterface: + return true + case .maxRetransmit: + return true + case .noChannel: + return true + case .tooLarge: + return false + case .noResponse: + return true + case .dutyCycleLimit: + return true + case .badRequest: + return true + case .notAuthorized: + return true + case .pkiFailed: + return false + case .pkiUnknownPubkey: + return false } } func protoEnumValue() -> Routing.Error { @@ -80,7 +128,10 @@ enum RoutingError: Int, CaseIterable, Identifiable { return Routing.Error.badRequest case .notAuthorized: return Routing.Error.notAuthorized - + case .pkiFailed: + return Routing.Error.pkiFailed + case .pkiUnknownPubkey: + return Routing.Error.pkiUnknownPubkey } } } diff --git a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift index 8f35002f..57babf4a 100644 --- a/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/ChannelEntityExtension.swift @@ -14,7 +14,7 @@ extension ChannelEntity { let context = PersistenceController.shared.container.viewContext let fetchRequest = MessageEntity.fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(key: "messageTimestamp", ascending: true)] - fetchRequest.predicate = NSPredicate(format: "channel == %ld AND toUser == nil AND isEmoji == false", self.index) + fetchRequest.predicate = NSPredicate(format: "channel == %ld AND toUser == nil AND isEmoji == false", self.index) return (try? context.fetch(fetchRequest)) ?? [MessageEntity]() } diff --git a/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift b/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift index 18d97f6c..70f36c3d 100644 --- a/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/MessageEntityExtension.swift @@ -19,7 +19,8 @@ extension MessageEntity { } var canRetry: Bool { - return ackError == 9 || ackError == 5 || ackError == 3 + let re = RoutingError(rawValue: Int(ackError)) + return re?.canRetry ?? false } var tapbacks: [MessageEntity] { diff --git a/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift index 7b07575b..68a48ee1 100644 --- a/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/MyInfoEntityExtension.swift @@ -22,6 +22,7 @@ extension MyInfoEntity { let unreadMessages = messageList.filter { ($0 as AnyObject).read == false && ($0 as AnyObject).isEmoji == false } return unreadMessages.count } + var hasAdmin: Bool { let adminChannel = channels?.filter { ($0 as AnyObject).name?.lowercased() == "admin" } return adminChannel?.count ?? 0 > 0 diff --git a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift index 66c915df..07ee9117 100644 --- a/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/NodeInfoEntityExtension.swift @@ -14,6 +14,10 @@ extension NodeInfoEntity { return self.positions?.lastObject as? PositionEntity } + var latestDeviceMetrics: TelemetryEntity? { + return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")).lastObject as? TelemetryEntity + } + var latestEnvironmentMetrics: TelemetryEntity? { return self.telemetries?.filtered(using: NSPredicate(format: "metricsType == 1")).lastObject as? TelemetryEntity } @@ -54,6 +58,15 @@ extension NodeInfoEntity { } return false } + + var canRemoteAdmin: Bool { + if UserDefaults.enableAdministration { + return true + } else { + let adminChannel = myInfo?.channels?.filter { ($0 as AnyObject).name?.lowercased() == "admin" } + return adminChannel?.count ?? 0 > 0 + } + } } public func createNodeInfo(num: Int64, context: NSManagedObjectContext) -> NodeInfoEntity { diff --git a/Meshtastic/Extensions/UserDefaults.swift b/Meshtastic/Extensions/UserDefaults.swift index ca8441be..740f04e2 100644 --- a/Meshtastic/Extensions/UserDefaults.swift +++ b/Meshtastic/Extensions/UserDefaults.swift @@ -71,6 +71,7 @@ extension UserDefaults { case modemPreset case firmwareVersion case environmentEnableWeatherKit + case enableAdministration case testIntEnum } @@ -162,6 +163,9 @@ extension UserDefaults { @UserDefault(.environmentEnableWeatherKit, defaultValue: true) static var environmentEnableWeatherKit: Bool + @UserDefault(.enableAdministration, defaultValue: false) + static var enableAdministration: Bool + @UserDefault(.testIntEnum, defaultValue: .one) static var testIntEnum: TestIntEnum } diff --git a/Meshtastic/Extensions/View.swift b/Meshtastic/Extensions/View.swift new file mode 100644 index 00000000..7349f36d --- /dev/null +++ b/Meshtastic/Extensions/View.swift @@ -0,0 +1,30 @@ +// +// View.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/14/24. +// + +import SwiftUI + +public extension View { + func onFirstAppear(_ action: @escaping () -> ()) -> some View { + modifier(FirstAppear(action: action)) + } +} + +private struct FirstAppear: ViewModifier { + let action: () -> () + + // Use this to only fire your block one time + @State private var hasAppeared = false + + func body(content: Content) -> some View { + // And then, track it here + content.onAppear { + guard !hasAppeared else { return } + hasAppeared = true + action() + } + } +} diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 4db1197c..2d8726ed 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -904,6 +904,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate lastConnectionError = "" isSubscribed = true Logger.mesh.info("🤜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID)") + sendTime() peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) // Config conplete returns so we don't read the characteristic again @@ -1011,6 +1012,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if toUserNum > 0 { newMessage.toUser = fetchedUsers.first(where: { $0.num == toUserNum }) newMessage.toUser?.lastMessage = Date() + if newMessage.toUser?.pkiEncrypted ?? false { + newMessage.publicKey = newMessage.toUser?.publicKey + newMessage.pkiEncrypted = true + } } newMessage.fromUser = fetchedUsers.first(where: { $0.num == fromUserNum }) newMessage.isEmoji = isEmoji @@ -1033,6 +1038,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = dataType var meshPacket = MeshPacket() + if newMessage.toUser?.pkiEncrypted ?? false { + meshPacket.pkiEncrypted = true + meshPacket.publicKey = newMessage.toUser?.publicKey ?? Data() + } meshPacket.id = UInt32(newMessage.messageId) if toUserNum > 0 { meshPacket.to = UInt32(toUserNum) @@ -1151,6 +1160,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate guard let lastLocation = LocationsHandler.shared.locationsArray.last else { return nil } + + if lastLocation == CLLocation(latitude: 0, longitude: 0) { + return nil + } + positionPacket.latitudeI = Int32(lastLocation.coordinate.latitude * 1e7) positionPacket.longitudeI = Int32(lastLocation.coordinate.longitude * 1e7) let timestamp = lastLocation.timestamp @@ -1296,9 +1310,36 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } + public func sendTime() -> Bool { + var adminPacket = AdminMessage() + adminPacket.setTimeOnly = UInt32(Date().timeIntervalSince1970) + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = 0 + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() adminPacket.shutdownSeconds = 5 + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1324,6 +1365,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func sendReboot(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootSeconds = 5 + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1349,6 +1393,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func sendRebootOta(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootOtaSeconds = 5 + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1374,6 +1421,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func sendEnterDfuMode(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.enterDfuModeRequest = true + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1399,7 +1449,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func sendFactoryReset(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() - adminPacket.factoryReset = 5 + adminPacket.factoryResetConfig = 5 + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1425,6 +1478,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func sendNodeDBReset(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.nodedbReset = 5 + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = 0 // UInt32(fromUser.num) @@ -1637,6 +1693,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setOwner = config + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1759,6 +1818,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setHamMode = ham + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1782,6 +1844,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func saveBluetoothConfig(config: Config.BluetoothConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.bluetooth = config + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1799,7 +1864,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved Bluetooth Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, context: context) + upsertBluetoothConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context) return Int64(meshPacket.id) } @@ -1810,7 +1875,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.setConfig.device = config - + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1827,7 +1894,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Device Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, context: context) + upsertDeviceConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context) return Int64(meshPacket.id) } return 0 @@ -1836,6 +1903,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate public func saveDisplayConfig(config: Config.DisplayConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.display = config + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1854,7 +1924,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded = dataMessage let messageDescription = "🛟 Saved Display Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, context: context) + upsertDisplayConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey, context: context) return Int64(meshPacket.id) } return 0 @@ -1864,6 +1934,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.setConfig.lora = config + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1881,7 +1954,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let messageDescription = "🛟 Saved LoRa Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription) { - upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, context: context) + upsertLoRaConfigPacket(config: config, nodeNum: toUser.num, sessionPasskey: toUser.userNode?.sessionPasskey,context: context) return Int64(meshPacket.id) } @@ -1892,7 +1965,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.setConfig.position = config - + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1954,7 +2029,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.setConfig.network = config - + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -1981,11 +2058,46 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } + public func saveSecurityConfig(config: Config.SecurityConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setConfig.security = config + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.channel = UInt32(adminIndex) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.ambientLighting = config - + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2015,7 +2127,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.setModuleConfig.cannedMessage = config - + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2045,7 +2159,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.setCannedMessageModuleMessages = messages - + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2076,7 +2192,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.setModuleConfig.detectionSensor = config - + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + + var adminPacket = AdminMessage() + adminPacket.getConfigRequest = AdminMessage.ConfigType.securityConfig + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.ambientlightingConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2660,7 +2815,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.cannedmsgConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2690,7 +2844,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.extnotifConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2720,7 +2873,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.paxcounterConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2750,7 +2902,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getRingtoneRequest = true - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2780,7 +2931,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.rangetestConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2810,7 +2960,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.mqttConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2840,7 +2989,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.detectionsensorConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2870,7 +3018,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.serialConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2900,7 +3047,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.storeforwardConfig - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) @@ -2930,7 +3076,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.telemetryConfig - + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index d2987e46..d3bef97a 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -49,7 +49,7 @@ func generateMessageMarkdown (message: String) -> String { } func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int64, nodeLongName: String) { - // We don't care about any of the Power settings, config is available for everything else + if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { upsertBluetoothConfigPacket(config: config.bluetooth, nodeNum: nodeNum, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { @@ -64,6 +64,8 @@ func localConfig (config: Config, context: NSManagedObjectContext, nodeNum: Int6 upsertPositionConfigPacket(config: config.position, nodeNum: nodeNum, context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { upsertPowerConfigPacket(config: config.power, nodeNum: nodeNum, context: context) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.security(config.security) { + upsertSecurityConfigPacket(config: config.security, nodeNum: nodeNum, context: context) } } @@ -196,7 +198,7 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo } } -func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NSManagedObjectContext) { +func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { if metadata.isInitialized { let logString = String.localizedStringWithFormat("mesh.log.device.metadata.received %@".localized, fromNum.toHex()) @@ -230,6 +232,10 @@ func deviceMetadataPacket (metadata: DeviceMetadata, fromNum: Int64, context: NS newNode.metadata = newMetadata } } + if sessionPasskey?.count != 0 { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() } catch { @@ -276,6 +282,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newTelemetries.append(telemetry) newNode.telemetries? = NSOrderedSet(array: newTelemetries) } + newNode.firstHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) newNode.snr = nodeInfo.snr @@ -296,6 +303,10 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } newUser.isLicensed = nodeInfo.user.isLicensed newUser.role = Int32(nodeInfo.user.role.rawValue) + if !nodeInfo.user.publicKey.isEmpty { + newUser.pkiEncrypted = true + newUser.publicKey = nodeInfo.user.publicKey + } newNode.user = newUser } else if nodeInfo.num > Constants.minimumNodeNum { let newUser = createUser(num: Int64(nodeInfo.num), context: context) @@ -353,6 +364,11 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje if fetchedNode[0].user == nil { fetchedNode[0].user = UserEntity(context: context) } + // Set the public key for a user if it is empty, don't update + if fetchedNode[0].user?.publicKey?.isEmpty == nil && !nodeInfo.user.publicKey.isEmpty { + fetchedNode[0].user?.pkiEncrypted = true + fetchedNode[0].user?.publicKey = nodeInfo.user.publicKey + } fetchedNode[0].user!.userId = nodeInfo.user.id fetchedNode[0].user!.num = Int64(nodeInfo.num) fetchedNode[0].user!.numString = String(nodeInfo.num) @@ -476,7 +492,7 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) { channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from), context: context) } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getDeviceMetadataResponse(adminMessage.getDeviceMetadataResponse) { - deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from), context: context) + deviceMetadataPacket(metadata: adminMessage.getDeviceMetadataResponse, fromNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getConfigResponse(adminMessage.getConfigResponse) { let config = adminMessage.getConfigResponse if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { @@ -493,6 +509,8 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { upsertPositionConfigPacket(config: config.position, nodeNum: Int64(packet.from), context: context) } else if config.payloadVariant == Config.OneOf_PayloadVariant.power(config.power) { upsertPowerConfigPacket(config: config.power, nodeNum: Int64(packet.from), context: context) + } else if config.payloadVariant == Config.OneOf_PayloadVariant.security(config.security) { + upsertSecurityConfigPacket(config: config.security, nodeNum: Int64(packet.from), sessionPasskey: adminMessage.sessionPasskey, context: context) } } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getModuleConfigResponse(adminMessage.getModuleConfigResponse) { let moduleConfig = adminMessage.getModuleConfigResponse @@ -614,13 +632,21 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana } } fetchedMessage[0].ackError = Int32(routingMessage.errorReason.rawValue) + if routingError == RoutingError.pkiFailed { + fetchedMessage[0].toUser?.keyMatch = false + fetchedMessage[0].toUser?.newPublicKey = fetchedMessage[0].publicKey + } if routingMessage.errorReason == Routing.Error.none { fetchedMessage[0].receivedACK = true } fetchedMessage[0].ackSNR = packet.rxSnr - fetchedMessage[0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime) + if packet.rxTime > 0 { + fetchedMessage[0].ackTimestamp = Int32(truncatingIfNeeded: packet.rxTime) + } else { + fetchedMessage[0].ackTimestamp = Int32(Date().timeIntervalSince1970) + } if fetchedMessage[0].toUser != nil { fetchedMessage[0].toUser!.objectWillChange.send() @@ -709,7 +735,11 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage return } mutableTelemetries.add(telemetry) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(truncatingIfNeeded: packet.rxTime))) + if packet.rxTime > 0 { + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(packet.rxTime)) + } else { + fetchedNode[0].lastHeard = Date() + } fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet } try context.save() @@ -808,13 +838,19 @@ func textMessageAppPacket( let fetchedUsers = try context.fetch(messageUsers) let newMessage = MessageEntity(context: context) newMessage.messageId = Int64(packet.id) - newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime) + if packet.rxTime > 0 { + newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime) + } else { + newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) + } newMessage.receivedACK = false newMessage.snr = packet.rxSnr newMessage.rssi = packet.rxRssi newMessage.isEmoji = packet.decoded.emoji == 1 newMessage.channel = Int32(packet.channel) newMessage.portNum = Int32(packet.decoded.portnum.rawValue) + newMessage.publicKey = packet.publicKey + newMessage.pkiEncrypted = packet.pkiEncrypted if packet.decoded.portnum == PortNum.detectionSensorApp { if !UserDefaults.enableDetectionNotifications { newMessage.read = true @@ -831,8 +867,21 @@ func textMessageAppPacket( } if fetchedUsers.first(where: { $0.num == packet.from }) != nil { newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from }) + if !(newMessage.fromUser?.publicKey?.isEmpty ?? true) { + /// We have a key, check if it matches + if newMessage.fromUser?.publicKey != newMessage.publicKey { + newMessage.fromUser?.keyMatch = false + newMessage.fromUser?.newPublicKey = newMessage.publicKey + } + } else { + /// We have no key, set it + newMessage.fromUser?.publicKey = packet.publicKey + newMessage.fromUser?.pkiEncrypted = packet.pkiEncrypted + } if packet.rxTime > 0 { newMessage.fromUser?.userNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } else { + newMessage.fromUser?.userNode?.lastHeard = Date() } } newMessage.messagePayload = messageText diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents index 6b2eaa00..40947b9f 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 42.xcdatamodel/contents @@ -155,7 +155,9 @@ + + @@ -224,6 +226,8 @@ + + @@ -245,6 +249,7 @@ + @@ -334,6 +339,17 @@ + + + + + + + + + + + @@ -418,11 +434,15 @@ + + + + diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index 93ba7f16..70e9f898 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -104,8 +104,8 @@ extension NSPersistentContainer { } /// Restore backup persistent stores located in the directory referenced by `backupURL`. - /// - /// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app. + /// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. + /// When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app. /// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores. /// - Throws: `CopyPersistentStoreError` in various situations. /// - Returns: Nothing. If no errors are thrown, the restore is complete. diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index c407f414..6f2520ee 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -148,6 +148,9 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) if packet.rxTime > 0 { newNode.firstHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } else { + newNode.firstHeard = Date() + newNode.lastHeard = Date() } newNode.snr = packet.rxSnr newNode.rssi = packet.rxRssi @@ -178,6 +181,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) newUser.role = Int32(newUserMessage.role.rawValue) newUser.hwModel = String(describing: newUserMessage.hwModel).uppercased() newUser.hwModelId = Int32(newUserMessage.hwModel.rawValue) + newUser.pkiEncrypted = packet.pkiEncrypted + newUser.publicKey = packet.publicKey Task { Api().loadDeviceHardwareData { (hw) in let dh = hw.first(where: { $0.hwModel == newUser.hwModelId }) @@ -231,9 +236,8 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].num = Int64(packet.from) if packet.rxTime > 0 { fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - if fetchedNode[0].firstHeard == nil { - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) - } + } else { + fetchedNode[0].lastHeard = Date() } fetchedNode[0].snr = packet.rxSnr fetchedNode[0].rssi = packet.rxRssi @@ -361,6 +365,8 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) } else if packet.rxTime > 0 { fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) + } else { + fetchedNode[0].lastHeard = Date() } fetchedNode[0].snr = packet.rxSnr fetchedNode[0].rssi = packet.rxRssi @@ -391,7 +397,7 @@ func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) } } -func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.bluetooth.config %@".localized, String(nodeNum)) MeshLogger.log("📶 \(logString)") @@ -416,6 +422,10 @@ func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, fetchedNode[0].bluetoothConfig?.fixedPin = Int32(config.fixedPin) fetchedNode[0].bluetoothConfig?.deviceLoggingEnabled = config.deviceLoggingEnabled } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [BluetoothConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -433,7 +443,7 @@ func upsertBluetoothConfigPacket(config: Config.BluetoothConfig, nodeNum: Int64, } } -func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.device.config %@".localized, String(nodeNum)) MeshLogger.log("📟 \(logString)") @@ -471,6 +481,10 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, conte fetchedNode[0].deviceConfig?.isManaged = config.isManaged fetchedNode[0].deviceConfig?.tzdef = config.tzdef } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [DeviceConfigEntity] Updated Device Config for node number: \(nodeNum.toHex(), privacy: .public)") @@ -486,7 +500,7 @@ func upsertDeviceConfigPacket(config: Config.DeviceConfig, nodeNum: Int64, conte } } -func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.display.config %@".localized, nodeNum.toHex()) MeshLogger.log("🖥️ \(logString)") @@ -512,7 +526,6 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, con newDisplayConfig.units = Int32(config.units.rawValue) newDisplayConfig.headingBold = config.headingBold fetchedNode[0].displayConfig = newDisplayConfig - } else { fetchedNode[0].displayConfig?.gpsFormat = Int32(config.gpsFormat.rawValue) @@ -525,7 +538,10 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, con fetchedNode[0].displayConfig?.units = Int32(config.units.rawValue) fetchedNode[0].displayConfig?.headingBold = config.headingBold } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() @@ -550,7 +566,7 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, con } } -func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.lora.config %@".localized, nodeNum.toHex()) MeshLogger.log("📻 \(logString)") @@ -598,6 +614,10 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, context: fetchedNode[0].loRaConfig?.ignoreMqtt = config.ignoreMqtt fetchedNode[0].loRaConfig?.sx126xRxBoostedGain = config.sx126XRxBoostedGain } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [LoRaConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -615,7 +635,7 @@ func upsertLoRaConfigPacket(config: Config.LoRaConfig, nodeNum: Int64, context: } } -func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.network.config %@".localized, String(nodeNum)) MeshLogger.log("🌐 \(logString)") @@ -640,7 +660,10 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, con fetchedNode[0].networkConfig?.wifiSsid = config.wifiSsid fetchedNode[0].networkConfig?.wifiPsk = config.wifiPsk } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [NetworkConfigEntity] Updated Network Config for node: \(nodeNum.toHex(), privacy: .public)") @@ -659,7 +682,7 @@ func upsertNetworkConfigPacket(config: Config.NetworkConfig, nodeNum: Int64, con } } -func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.position.config %@".localized, String(nodeNum)) MeshLogger.log("🗺️ \(logString)") @@ -702,6 +725,10 @@ func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, c fetchedNode[0].positionConfig?.gpsUpdateInterval = Int32(config.gpsUpdateInterval) fetchedNode[0].positionConfig?.positionFlags = Int32(config.positionFlags) } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [PositionConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -719,7 +746,7 @@ func upsertPositionConfigPacket(config: Config.PositionConfig, nodeNum: Int64, c } } -func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.power.config %@".localized, String(nodeNum)) MeshLogger.log("🗺️ \(logString)") @@ -749,6 +776,10 @@ func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, context fetchedNode[0].powerConfig?.onBatteryShutdownAfterSecs = Int32(truncatingIfNeeded: config.onBatteryShutdownAfterSecs) fetchedNode[0].powerConfig?.waitBluetoothSecs = Int32(truncatingIfNeeded: config.waitBluetoothSecs) } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [PowerConfigEntity] Updated Power Config for node: \(nodeNum.toHex(), privacy: .public)") @@ -766,7 +797,60 @@ func upsertPowerConfigPacket(config: Config.PowerConfig, nodeNum: Int64, context } } -func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertSecurityConfigPacket(config: Config.SecurityConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { + + let logString = String.localizedStringWithFormat("mesh.log.security.config %@".localized, String(nodeNum)) + MeshLogger.log("🛡️ \(logString)") + + let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + let fetchedNode = try context.fetch(fetchNodeInfoRequest) + // Found a node, save Security Config + if !fetchedNode.isEmpty { + if fetchedNode[0].securityConfig == nil { + let newSecurityConfig = SecurityConfigEntity(context: context) + newSecurityConfig.publicKey = config.publicKey + newSecurityConfig.privateKey = config.privateKey + newSecurityConfig.adminKey = config.adminKey + newSecurityConfig.isManaged = config.isManaged + newSecurityConfig.serialEnabled = config.serialEnabled + newSecurityConfig.debugLogApiEnabled = config.debugLogApiEnabled + newSecurityConfig.bluetoothLoggingEnabled = config.bluetoothLoggingEnabled + fetchedNode[0].securityConfig = newSecurityConfig + } else { + fetchedNode[0].securityConfig?.publicKey = config.publicKey + fetchedNode[0].securityConfig?.privateKey = config.privateKey + fetchedNode[0].securityConfig?.adminKey = config.adminKey + fetchedNode[0].securityConfig?.isManaged = config.isManaged + fetchedNode[0].securityConfig?.serialEnabled = config.serialEnabled + fetchedNode[0].securityConfig?.debugLogApiEnabled = config.debugLogApiEnabled + fetchedNode[0].securityConfig?.bluetoothLoggingEnabled = config.bluetoothLoggingEnabled + } + if sessionPasskey?.count != 0 { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } + do { + try context.save() + Logger.data.info("💾 [SecurityConfigEntity] Updated Security Config for node: \(nodeNum.toHex(), privacy: .public)") + + } catch { + context.rollback() + let nsError = error as NSError + Logger.data.error("💥 [SecurityConfigEntity] Error Updating Core Data: \(nsError, privacy: .public)") + } + } else { + Logger.data.error("💥 [SecurityConfigEntity] No Nodes found in local database matching node \(nodeNum.toHex(), privacy: .public) unable to save Security Config") + } + } catch { + let nsError = error as NSError + Logger.data.error("💥 [SecurityConfigEntity] Fetching node for core data failed: \(nsError, privacy: .public)") + } +} + +func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightingConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.ambientlighting.config %@".localized, String(nodeNum)) MeshLogger.log("🏮 \(logString)") @@ -780,16 +864,13 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin if !fetchedNode.isEmpty { if fetchedNode[0].cannedMessageConfig == nil { - let newAmbientLightingConfig = AmbientLightingConfigEntity(context: context) - newAmbientLightingConfig.ledState = config.ledState newAmbientLightingConfig.current = Int32(config.current) newAmbientLightingConfig.red = Int32(config.red) newAmbientLightingConfig.green = Int32(config.green) newAmbientLightingConfig.blue = Int32(config.blue) fetchedNode[0].ambientLightingConfig = newAmbientLightingConfig - } else { if fetchedNode[0].ambientLightingConfig == nil { @@ -801,7 +882,10 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin fetchedNode[0].ambientLightingConfig?.green = Int32(config.green) fetchedNode[0].ambientLightingConfig?.blue = Int32(config.blue) } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [AmbientLightingConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -819,7 +903,7 @@ func upsertAmbientLightingModuleConfigPacket(config: ModuleConfig.AmbientLightin } } -func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.cannedmessage.config %@".localized, String(nodeNum)) MeshLogger.log("🥫 \(logString)") @@ -833,9 +917,7 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo if !fetchedNode.isEmpty { if fetchedNode[0].cannedMessageConfig == nil { - let newCannedMessageConfig = CannedMessageConfigEntity(context: context) - newCannedMessageConfig.enabled = config.enabled newCannedMessageConfig.sendBell = config.sendBell newCannedMessageConfig.rotary1Enabled = config.rotary1Enabled @@ -846,11 +928,8 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo newCannedMessageConfig.inputbrokerEventCw = Int32(config.inputbrokerEventCw.rawValue) newCannedMessageConfig.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue) newCannedMessageConfig.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue) - fetchedNode[0].cannedMessageConfig = newCannedMessageConfig - } else { - fetchedNode[0].cannedMessageConfig?.enabled = config.enabled fetchedNode[0].cannedMessageConfig?.sendBell = config.sendBell fetchedNode[0].cannedMessageConfig?.rotary1Enabled = config.rotary1Enabled @@ -862,7 +941,10 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo fetchedNode[0].cannedMessageConfig?.inputbrokerEventCcw = Int32(config.inputbrokerEventCcw.rawValue) fetchedNode[0].cannedMessageConfig?.inputbrokerEventPress = Int32(config.inputbrokerEventPress.rawValue) } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [CannedMessageConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -880,7 +962,7 @@ func upsertCannedMessagesModuleConfigPacket(config: ModuleConfig.CannedMessageCo } } -func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSensorConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) MeshLogger.log("🕵️ \(logString)") @@ -892,21 +974,17 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso let fetchedNode = try context.fetch(fetchNodeInfoRequest) // Found a node, save Detection Sensor Config if !fetchedNode.isEmpty { - if fetchedNode[0].detectionSensorConfig == nil { - let newConfig = DetectionSensorConfigEntity(context: context) newConfig.enabled = config.enabled newConfig.sendBell = config.sendBell newConfig.name = config.name - newConfig.monitorPin = Int32(config.monitorPin) newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh newConfig.usePullup = config.usePullup newConfig.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs) newConfig.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs) fetchedNode[0].detectionSensorConfig = newConfig - } else { fetchedNode[0].detectionSensorConfig?.enabled = config.enabled fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell @@ -917,7 +995,10 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(truncatingIfNeeded: config.minimumBroadcastSecs) fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(truncatingIfNeeded: config.stateBroadcastSecs) } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [DetectionSensorConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -938,7 +1019,7 @@ func upsertDetectionSensorModuleConfigPacket(config: ModuleConfig.DetectionSenso } } -func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum)) MeshLogger.log("📣 \(logString)") @@ -969,7 +1050,6 @@ func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalN newExternalNotificationConfig.nagTimeout = Int32(config.nagTimeout) newExternalNotificationConfig.useI2SAsBuzzer = config.useI2SAsBuzzer fetchedNode[0].externalNotificationConfig = newExternalNotificationConfig - } else { fetchedNode[0].externalNotificationConfig?.enabled = config.enabled fetchedNode[0].externalNotificationConfig?.usePWM = config.usePwm @@ -987,7 +1067,10 @@ func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalN fetchedNode[0].externalNotificationConfig?.nagTimeout = Int32(config.nagTimeout) fetchedNode[0].externalNotificationConfig?.useI2SAsBuzzer = config.useI2SAsBuzzer } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [ExternalNotificationConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -1005,7 +1088,7 @@ func upsertExternalNotificationModuleConfigPacket(config: ModuleConfig.ExternalN } } -func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.paxcounter.config %@".localized, String(nodeNum)) MeshLogger.log("🧑‍🤝‍🧑 \(logString)") @@ -1017,19 +1100,19 @@ func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, n let fetchedNode = try context.fetch(fetchNodeInfoRequest) // Found a node, save PAX Counter Config if !fetchedNode.isEmpty { - if fetchedNode[0].paxCounterConfig == nil { let newPaxCounterConfig = PaxCounterConfigEntity(context: context) newPaxCounterConfig.enabled = config.enabled newPaxCounterConfig.updateInterval = Int32(config.paxcounterUpdateInterval) - fetchedNode[0].paxCounterConfig = newPaxCounterConfig - } else { fetchedNode[0].paxCounterConfig?.enabled = config.enabled fetchedNode[0].paxCounterConfig?.updateInterval = Int32(config.paxcounterUpdateInterval) } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [PaxCounterConfigEntity] Updated for node number: \(nodeNum.toHex(), privacy: .public)") @@ -1047,7 +1130,7 @@ func upsertPaxCounterModuleConfigPacket(config: ModuleConfig.PaxcounterConfig, n } } -func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.ringtone.config %@".localized, String(nodeNum)) MeshLogger.log("⛰️ \(logString)") @@ -1066,6 +1149,10 @@ func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, context: NSManage } else { fetchedNode[0].rtttlConfig?.ringtone = ringtone } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [RtttlConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -1083,7 +1170,7 @@ func upsertRtttlConfigPacket(ringtone: String, nodeNum: Int64, context: NSManage } } -func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.mqtt.config %@".localized, String(nodeNum)) MeshLogger.log("🌉 \(logString)") @@ -1095,7 +1182,6 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6 let fetchedNode = try context.fetch(fetchNodeInfoRequest) // Found a node, save MQTT Config if !fetchedNode.isEmpty { - if fetchedNode[0].mqttConfig == nil { let newMQTTConfig = MQTTConfigEntity(context: context) newMQTTConfig.enabled = config.enabled @@ -1125,6 +1211,10 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6 fetchedNode[0].mqttConfig?.mapPositionPrecision = Int32(config.mapReportSettings.positionPrecision) fetchedNode[0].mqttConfig?.mapPublishIntervalSecs = Int32(config.mapReportSettings.publishIntervalSecs) } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [MQTTConfigEntity] Updated for node number: \(nodeNum.toHex(), privacy: .public)") @@ -1142,7 +1232,7 @@ func upsertMqttModuleConfigPacket(config: ModuleConfig.MQTTConfig, nodeNum: Int6 } } -func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.rangetest.config %@".localized, String(nodeNum)) MeshLogger.log("⛰️ \(logString)") @@ -1165,6 +1255,10 @@ func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nod fetchedNode[0].rangeTestConfig?.enabled = config.enabled fetchedNode[0].rangeTestConfig?.save = config.save } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [RangeTestConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -1182,7 +1276,7 @@ func upsertRangeTestModuleConfigPacket(config: ModuleConfig.RangeTestConfig, nod } } -func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.serial.config %@".localized, String(nodeNum)) MeshLogger.log("🤖 \(logString)") @@ -1194,9 +1288,7 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: let fetchedNode = try context.fetch(fetchNodeInfoRequest) // Found a node, save Device Config if !fetchedNode.isEmpty { - if fetchedNode[0].serialConfig == nil { - let newSerialConfig = SerialConfigEntity(context: context) newSerialConfig.enabled = config.enabled newSerialConfig.echo = config.echo @@ -1206,7 +1298,6 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: newSerialConfig.timeout = Int32(config.timeout) newSerialConfig.mode = Int32(config.mode.rawValue) fetchedNode[0].serialConfig = newSerialConfig - } else { fetchedNode[0].serialConfig?.enabled = config.enabled fetchedNode[0].serialConfig?.echo = config.echo @@ -1216,7 +1307,10 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: fetchedNode[0].serialConfig?.timeout = Int32(config.timeout) fetchedNode[0].serialConfig?.mode = Int32(config.mode.rawValue) } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [SerialConfigEntity]Updated Serial Module Config for node: \(nodeNum.toHex(), privacy: .public)") @@ -1237,7 +1331,7 @@ func upsertSerialModuleConfigPacket(config: ModuleConfig.SerialConfig, nodeNum: } } -func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum)) MeshLogger.log("📬 \(logString)") @@ -1249,9 +1343,7 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi let fetchedNode = try context.fetch(fetchNodeInfoRequest) // Found a node, save Store & Forward Sensor Config if !fetchedNode.isEmpty { - if fetchedNode[0].storeForwardConfig == nil { - let newConfig = StoreForwardConfigEntity(context: context) newConfig.enabled = config.enabled newConfig.heartbeat = config.heartbeat @@ -1259,7 +1351,6 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi newConfig.historyReturnMax = Int32(config.historyReturnMax) newConfig.historyReturnWindow = Int32(config.historyReturnWindow) fetchedNode[0].storeForwardConfig = newConfig - } else { fetchedNode[0].storeForwardConfig?.enabled = config.enabled fetchedNode[0].storeForwardConfig?.heartbeat = config.heartbeat @@ -1267,6 +1358,10 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi fetchedNode[0].storeForwardConfig?.historyReturnMax = Int32(config.historyReturnMax) fetchedNode[0].storeForwardConfig?.historyReturnWindow = Int32(config.historyReturnWindow) } + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [StoreForwardConfigEntity] Updated for node: \(nodeNum.toHex(), privacy: .public)") @@ -1284,21 +1379,18 @@ func upsertStoreForwardModuleConfigPacket(config: ModuleConfig.StoreForwardConfi } } -func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nodeNum: Int64, context: NSManagedObjectContext) { +func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nodeNum: Int64, sessionPasskey: Data? = Data(), context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.telemetry.config %@".localized, String(nodeNum)) MeshLogger.log("📈 \(logString)") let fetchNodeInfoRequest = NodeInfoEntity.fetchRequest() fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - do { let fetchedNode = try context.fetch(fetchNodeInfoRequest) // Found a node, save Telemetry Config if !fetchedNode.isEmpty { - if fetchedNode[0].telemetryConfig == nil { - let newTelemetryConfig = TelemetryConfigEntity(context: context) newTelemetryConfig.deviceUpdateInterval = Int32(config.deviceUpdateInterval) newTelemetryConfig.environmentUpdateInterval = Int32(config.environmentUpdateInterval) @@ -1309,7 +1401,6 @@ func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nod newTelemetryConfig.powerUpdateInterval = Int32(config.powerUpdateInterval) newTelemetryConfig.powerScreenEnabled = config.powerScreenEnabled fetchedNode[0].telemetryConfig = newTelemetryConfig - } else { fetchedNode[0].telemetryConfig?.deviceUpdateInterval = Int32(config.deviceUpdateInterval) fetchedNode[0].telemetryConfig?.environmentUpdateInterval = Int32(config.environmentUpdateInterval) @@ -1320,7 +1411,10 @@ func upsertTelemetryModuleConfigPacket(config: ModuleConfig.TelemetryConfig, nod fetchedNode[0].telemetryConfig?.powerUpdateInterval = Int32(config.powerUpdateInterval) fetchedNode[0].telemetryConfig?.powerScreenEnabled = config.powerScreenEnabled } - + if sessionPasskey != nil { + fetchedNode[0].sessionPasskey = sessionPasskey + fetchedNode[0].sessionExpiration = Date().addingTimeInterval(300) + } do { try context.save() Logger.data.info("💾 [TelemetryConfigEntity] Updated Telemetry Module Config for node: \(nodeNum.toHex(), privacy: .public)") diff --git a/Meshtastic/Resources/DeviceHardware.json b/Meshtastic/Resources/DeviceHardware.json index 09eb2e6c..cbad8df9 100644 --- a/Meshtastic/Resources/DeviceHardware.json +++ b/Meshtastic/Resources/DeviceHardware.json @@ -370,7 +370,7 @@ { "hwModel": 66, "hwModelSlug": "HELTEC_VISION_MASTER_T190", - "platformioTarget": "heltec-vision-master-T190", + "platformioTarget": "heltec-vision-master-t190", "architecture": "esp32-s3", "activelySupported": true, "displayName": "Heltec Vision Master T190" diff --git a/Meshtastic/Router/NavigationState.swift b/Meshtastic/Router/NavigationState.swift index b33fc48a..0ff61108 100644 --- a/Meshtastic/Router/NavigationState.swift +++ b/Meshtastic/Router/NavigationState.swift @@ -46,6 +46,7 @@ enum SettingsNavigationState: String { case paxCounter case ringtone case serial + case security case storeAndForward case telemetry case meshLog diff --git a/Meshtastic/Tips/MessagesTips.swift b/Meshtastic/Tips/MessagesTips.swift index d78daa0e..4771e386 100644 --- a/Meshtastic/Tips/MessagesTips.swift +++ b/Meshtastic/Tips/MessagesTips.swift @@ -25,22 +25,3 @@ struct MessagesTip: Tip { Image(systemName: "bubble.left.and.bubble.right") } } - -@available(iOS 17.0, macOS 14.0, *) -struct ContactsTip: Tip { - - var id: String { - return "tip.messages.contacts" - } - var title: Text { - // Text("tip.messages.contacts.title") - Text("Contacts") - } - var message: Text? { - // Text("tip.messages.contacts.message") - Text("Each node is an available contact. Contacts with recent messages or marked as favorites show up at the top of the list. Select a contact to send or view messages. Long press to favorite or mute the contact or delete the conversation.") - } - var image: Image? { - Image(systemName: "person.circle") - } -} diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index b109a318..e8384c35 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -8,7 +8,7 @@ import SwiftUI struct ContentView: View { @ObservedObject var appState: AppState - + @ObservedObject var router: Router diff --git a/Meshtastic/Views/Helpers/BatteryCompact.swift b/Meshtastic/Views/Helpers/BatteryCompact.swift index 28f62a57..bc714da6 100644 --- a/Meshtastic/Views/Helpers/BatteryCompact.swift +++ b/Meshtastic/Views/Helpers/BatteryCompact.swift @@ -7,7 +7,7 @@ import SwiftUI struct BatteryCompact: View { - @State var batteryLevel: Int32 + var batteryLevel: Int32 var font: Font var iconFont: Font var color: Color diff --git a/Meshtastic/Views/Helpers/BatteryLevelCompact.swift b/Meshtastic/Views/Helpers/BatteryLevelCompact.swift deleted file mode 100644 index e1354e23..00000000 --- a/Meshtastic/Views/Helpers/BatteryLevelCompact.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// BatteryIcon.swift -// Meshtastic -// -// Copyright Garth Vander Houwen 3/24/23. -// -import SwiftUI - -struct BatteryLevelCompact: View { - - @ObservedObject var node: NodeInfoEntity - - var font: Font - var iconFont: Font - var color: Color - - var body: some View { - let deviceMetrics = node.telemetries?.filtered(using: NSPredicate(format: "metricsType == 0")) - let mostRecent = deviceMetrics?.lastObject as? TelemetryEntity - let batteryLevel = mostRecent?.batteryLevel ?? 0 - if deviceMetrics?.count ?? 0 > 0 { - BatteryCompact(batteryLevel: batteryLevel, font: font, iconFont: iconFont, color: color) - } - } -} diff --git a/Meshtastic/Views/Helpers/Help/AckErrors.swift b/Meshtastic/Views/Helpers/Help/AckErrors.swift new file mode 100644 index 00000000..c20a58f9 --- /dev/null +++ b/Meshtastic/Views/Helpers/Help/AckErrors.swift @@ -0,0 +1,44 @@ +// +// IAQScale.swift +// Meshtastic +// +// Copyright Garth Vander Houwen 4/24/24. +// + +import SwiftUI + +struct AckErrors: View { + + var body: some View { + VStack(alignment: .leading) { + Text("Message Status Options") + .font(.title2) + HStack { + RoundedRectangle(cornerRadius: 5) + .fill(.orange) + .frame(width: 20, height: 12) + Text("Acknowledged by another node") + .font(.caption) + .foregroundStyle(.orange) + } + ForEach(RoutingError.allCases) { re in + HStack { + RoundedRectangle(cornerRadius: 5) + .fill(re.color) + .frame(width: 20, height: 12) + Text(re.display) + .font(.caption) + .foregroundStyle(re.color) + } + } + } + } +} + +struct AckErrorsPreviews: PreviewProvider { + static var previews: some View { + VStack { + AckErrors() + } + } +} diff --git a/Meshtastic/Views/Helpers/Help/DirectMessagesHelp.swift b/Meshtastic/Views/Helpers/Help/DirectMessagesHelp.swift new file mode 100644 index 00000000..fd0f0414 --- /dev/null +++ b/Meshtastic/Views/Helpers/Help/DirectMessagesHelp.swift @@ -0,0 +1,76 @@ +// +// DirectMessagesHelp.swift +// Meshtastic +// +// Copyright Garth Vander Houwen on 8/15/24. +// + +import SwiftUI + +struct DirectMessagesHelp: View { + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + @Environment(\.dismiss) private var dismiss + + var body: some View { + ScrollView { + Label("Direct Message Help", systemImage: "questionmark.circle") + .font(.title) + .padding(.vertical) + VStack(alignment: .leading) { + HStack { + Image(systemName: "star.fill") + .foregroundColor(.yellow) + .padding(.bottom) + Text("Favorites and nodes with recent messages show up at the top of the contact list.") + .fixedSize(horizontal: false, vertical: true) + .padding(.bottom) + } + HStack { + Image(systemName: "hand.tap") + .padding(.bottom) + Text("Long press to favorite or mute the contact or delete a conversation.") + .fixedSize(horizontal: false, vertical: true) + .padding(.bottom) + } + } + if idiom == .phone { + VStack(alignment: .leading) { + LockLegend() + AckErrors() + } + } else { + HStack(alignment: .top) { + LockLegend() + AckErrors() + .padding(.trailing) + } + } +#if targetEnvironment(macCatalyst) + Spacer() + Button { + dismiss() + } label: { + Label("close", systemImage: "xmark") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) +#endif + } + .frame(minHeight: 0, maxHeight: .infinity, alignment: .leading) + .padding() + .presentationDetents([.large]) + .presentationContentInteraction(.scrolls) + .presentationDragIndicator(.visible) + .presentationBackgroundInteraction(.enabled(upThrough: .large)) + } +} + +struct DirectMessagesHelpPreviews: PreviewProvider { + static var previews: some View { + VStack { + AckErrors() + } + } +} diff --git a/Meshtastic/Views/Helpers/Help/LockLegend.swift b/Meshtastic/Views/Helpers/Help/LockLegend.swift new file mode 100644 index 00000000..36716798 --- /dev/null +++ b/Meshtastic/Views/Helpers/Help/LockLegend.swift @@ -0,0 +1,66 @@ +// +// LockLegend.swift +// Meshtastic +// +// Copyright Garth Vander Houwen 8/15/24. +// + +import SwiftUI + +struct LockLegend: View { + + var body: some View { + VStack(alignment: .leading) { + Text("What does the lock mean?") + .font(.title2) + .padding(.bottom, 5) + VStack(alignment: .leading) { + HStack { + Image(systemName: "lock.open.fill") + .foregroundColor(.yellow) + Text("Shared Key") + .fontWeight(.semibold) + } + Text("Direct messages are using the shared key for the channel.") + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) + .font(.callout) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.bottom) + VStack(alignment: .leading) { + HStack { + Image(systemName: "lock.fill") + .foregroundColor(.green) + Text("Public Key Encryption") + .fontWeight(.semibold) + } + Text("Direct messages are using the new public key infrastructure for encryption. Reguires firmware version 2.5 or greater.") + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) + .font(.callout) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.bottom) + VStack(alignment: .leading) { + HStack { + Image(systemName: "key.slash") + .foregroundColor(.red) + Text("Public Key Mismatch") + .fontWeight(.semibold) + } + Text("The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.") + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) + .font(.callout) + .fixedSize(horizontal: false, vertical: true) + } + .padding(.bottom) + } + } +} + +struct LockLegendPreviews: PreviewProvider { + static var previews: some View { + VStack { + LockLegend() + } + } +} diff --git a/Meshtastic/Views/Helpers/SecureInput.swift b/Meshtastic/Views/Helpers/SecureInput.swift new file mode 100644 index 00000000..a2994747 --- /dev/null +++ b/Meshtastic/Views/Helpers/SecureInput.swift @@ -0,0 +1,62 @@ +// +// SecureInput.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/12/24. +// + +import SwiftUI + +struct SecureInput: View { + + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + @Binding private var text: String + @Binding private var isValid: Bool + @State private var isSecure: Bool = true + private var title: String + + init(_ title: String, text: Binding, isValid: Binding) { + self.title = title + self._text = text + self._isValid = isValid + } + + var body: some View { + ZStack(alignment: .trailing) { + Group { + if isSecure { + SecureField(title, text: $text) + .font(idiom == .phone ? .caption : .callout) + .allowsTightening(true) + .monospaced() + .keyboardType(.alphabet) + .foregroundStyle(.tertiary) + .disableAutocorrection(true) + } else { + TextField(title, text: $text, axis: .vertical) + .font(idiom == .phone ? .caption : .callout) + .allowsTightening(true) + .monospaced() + .keyboardType(.alphabet) + .foregroundStyle(.tertiary) + .disableAutocorrection(true) + .textSelection(.enabled) + .lineLimit(...3) + .background( + RoundedRectangle(cornerRadius: 10.0) + .stroke(isValid ? Color.clear : Color.red, lineWidth: 2.0) + ) + } + }.padding(.trailing, 36) + + if !text.isEmpty { + Button(action: { + isSecure.toggle() + }) { + Image(systemName: self.isSecure ? "eye.slash" : "eye") + .accentColor(.secondary) + } + } + } + } +} diff --git a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift index 8f057610..eb93897f 100644 --- a/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift +++ b/Meshtastic/Views/Helpers/Weather/LocalWeatherConditions.swift @@ -98,8 +98,9 @@ struct WeatherConditionsCompactWidget: View { Label { Text(description) } icon: { Image(systemName: symbolName).symbolRenderingMode(.multicolor) } .font(.caption) Text(temperature) - .font(temperature.length < 4 ? .system(size: 90) : .system(size: 60) ) + .font(temperature.length < 4 ? .system(size: 80) : .system(size: 60) ) } + .padding(10) .frame(maxWidth: .infinity) .frame(height: 175) .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) @@ -121,7 +122,7 @@ struct HumidityCompactWidget: View { .fixedSize(horizontal: false, vertical: true) .font(.caption) } - .padding(.horizontal) + .padding(10) .frame(maxWidth: .infinity) .frame(height: 175) .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) @@ -135,14 +136,14 @@ struct PressureCompactWidget: View { var body: some View { VStack(alignment: .leading) { Label { Text("PRESSURE") } icon: { Image(systemName: "gauge").symbolRenderingMode(.multicolor) } - .font(.caption2) + .font(.caption) Text(pressure) .font(pressure.length < 7 ? .system(size: 35) : .system(size: 30) ) Text(low ? "LOW" : "HIGH") .padding(.bottom) Text(unit) } - .padding(.horizontal) + .padding(10) .frame(maxWidth: .infinity) .frame(height: 175) .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) @@ -156,7 +157,6 @@ struct WindCompactWidget: View { var body: some View { VStack(alignment: .leading) { Label { Text("WIND") } icon: { Image(systemName: "wind").foregroundColor(.accentColor) } - .font(.caption) Text("\(direction)") .font(.caption) .padding(.bottom, 10) @@ -164,7 +164,7 @@ struct WindCompactWidget: View { .font(.system(size: 35)) Text("Gusts \(gust)") } - .padding(.horizontal) + .padding(10) .frame(maxWidth: .infinity) .frame(height: 175) .background(.tertiary, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index b996e790..c0852a36 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -84,16 +84,16 @@ struct ChannelMessageList: View { } HStack { + let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) if currentUser && message.receivedACK { - // Ack Received - Text("Acknowledged").font(.caption2).foregroundColor(.gray) + Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) + .foregroundStyle(ackErrorVal?.color ?? Color.red) + .font(.caption2) } else if currentUser && message.ackError == 0 { // Empty Error - Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange) - } else if currentUser && message.ackError > 0 { - let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) - Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) - .font(.caption2).foregroundColor(.red) + Text("Waiting to be acknowledged. . .").font( + .caption2) + .foregroundColor(.orange) } else if isDetectionSensorMessage { let messageDate = message.timestamp Text(" \(messageDate.formattedDate(format: MessageText.dateFormatString))").font(.caption2).foregroundColor(.gray) diff --git a/Meshtastic/Views/Messages/MessageContextMenuItems.swift b/Meshtastic/Views/Messages/MessageContextMenuItems.swift index ab363f59..c9c37cdc 100644 --- a/Meshtastic/Views/Messages/MessageContextMenuItems.swift +++ b/Meshtastic/Views/Messages/MessageContextMenuItems.swift @@ -13,6 +13,9 @@ struct MessageContextMenuItems: View { var body: some View { VStack { + if message.pkiEncrypted { + Label("Encrypted", systemImage: "lock") + } Text("channel") + Text(": \(message.channel)") } @@ -53,6 +56,7 @@ struct MessageContextMenuItems: View { let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp)) Text("\(messageDate.formattedDate(format: MessageText.dateFormatString))").foregroundColor(.gray) } + if !isCurrentUser && !(message.fromUser?.userNode?.viaMqtt ?? false) && message.fromUser?.userNode?.hopsAway ?? -1 == 0 { VStack { Text("SNR \(String(format: "%.2f", message.snr)) dB") @@ -60,7 +64,7 @@ struct MessageContextMenuItems: View { } } else if !isCurrentUser && !(message.fromUser?.userNode?.viaMqtt ?? false) { VStack { - Text("Hops Away \(message.fromUser?.userNode?.hopsAway ?? 0)) dB") + Text("Hops Away \(message.fromUser?.userNode?.hopsAway ?? 0)") } } if isCurrentUser && message.receivedACK { diff --git a/Meshtastic/Views/Messages/MessageText.swift b/Meshtastic/Views/Messages/MessageText.swift index 0cdd4be8..93f8cf25 100644 --- a/Meshtastic/Views/Messages/MessageText.swift +++ b/Meshtastic/Views/Messages/MessageText.swift @@ -24,11 +24,25 @@ struct MessageText: View { let markdownText = LocalizedStringKey(message.messagePayloadMarkdown ?? (message.messagePayload ?? "EMPTY MESSAGE")) return Text(markdownText) .tint(Self.linkBlue) - .padding(10) + .padding(.vertical, 10) + .padding(.horizontal, 8) .foregroundColor(.white) .background(isCurrentUser ? .accentColor : Color(.gray)) .cornerRadius(15) .overlay { + if message.pkiEncrypted { + VStack(alignment: .trailing) { + Spacer() + HStack { + Spacer() + Image(systemName: "lock.circle.fill") + .symbolRenderingMode(.palette) + .foregroundStyle(.white, .green) + .font(.system(size: 20)) + .offset(x: 8, y: 8) + } + } + } let isDetectionSensorMessage = message.portNum == Int32(PortNum.detectionSensorApp.rawValue) if tapBackDestination.overlaySensorMessage { VStack { diff --git a/Meshtastic/Views/Messages/UserList.swift b/Meshtastic/Views/Messages/UserList.swift index 13eb837d..cb3582ab 100644 --- a/Meshtastic/Views/Messages/UserList.swift +++ b/Meshtastic/Views/Messages/UserList.swift @@ -20,6 +20,7 @@ struct UserList: View { @State private var viaLora = true @State private var viaMqtt = true @State private var isOnline = false + @State private var isPkiEncrypted = false @State private var isFavorite = false @State private var isEnvironment = false @State private var distanceFilter = false @@ -27,11 +28,23 @@ struct UserList: View { @State private var hopsAway: Double = -1.0 @State private var roleFilter = false @State private var deviceRoles: Set = [] - @State var isEditingFilters = false + @State private var editingFilters = false + @State private var showingHelp = false + @State private var showingTrustConfirm: Bool = false + + var boolFilters: [Bool] {[ + isFavorite, + isOnline, + isPkiEncrypted, + isEnvironment, + distanceFilter, + roleFilter + ]} @FetchRequest( sortDescriptors: [NSSortDescriptor(key: "lastMessage", ascending: false), NSSortDescriptor(key: "userNode.favorite", ascending: false), + NSSortDescriptor(key: "pkiEncrypted", ascending: false), NSSortDescriptor(key: "longName", ascending: true)], animation: .default ) @@ -47,9 +60,6 @@ struct UserList: View { let dateFormatString = (localeDateFormat ?? "MM/dd/YY") VStack { List(selection: $userSelection) { - if #available(iOS 17.0, macOS 14.0, *) { - TipView(ContactsTip(), arrowEdge: .bottom) - } ForEach(users) { (user: UserEntity) in let mostRecent = user.messageList.last let lastMessageTime = Date(timeIntervalSince1970: TimeInterval(Int64((mostRecent?.messageTimestamp ?? 0 )))) @@ -69,8 +79,22 @@ struct UserList: View { VStack(alignment: .leading) { HStack { + if user.pkiEncrypted { + if !user.keyMatch { + /// Public Key on the User and the Public Key on the Last Message don't match + Image(systemName: "key.slash") + .foregroundColor(.red) + } else { + Image(systemName: "lock.fill") + .foregroundColor(.green) + } + } else { + Image(systemName: "lock.open.fill") + .foregroundColor(.yellow) + } Text(user.longName ?? "unknown".localized) .font(.headline) + .allowsTightening(true) Spacer() if user.userNode?.favorite ?? false { Image(systemName: "star.fill") @@ -169,9 +193,12 @@ struct UserList: View { } } .listStyle(.plain) - .navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count - 1))) - .sheet(isPresented: $isEditingFilters) { - NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isFavorite: $isFavorite, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles) + .navigationTitle(String.localizedStringWithFormat("contacts %@".localized, String(users.count == 0 ? 0 : users.count))) + .sheet(isPresented: $editingFilters) { + NodeListFilter(filterTitle: "Contact Filters", viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, maximumDistance: $maxDistance, hopsAway: $hopsAway, roleFilter: $roleFilter, deviceRoles: $deviceRoles) + } + .sheet(isPresented: $showingHelp) { + DirectMessagesHelp() } .onChange(of: searchText) { _ in searchUserList() @@ -194,40 +221,46 @@ struct UserList: View { .onChange(of: hopsAway) { _ in searchUserList() } - .onChange(of: isOnline) { _ in - searchUserList() - } - .onChange(of: isFavorite) { _ in + .onChange(of: [boolFilters]) { _ in searchUserList() } .onChange(of: maxDistance) { _ in searchUserList() } - .onChange(of: distanceFilter) { _ in + .onFirstAppear { searchUserList() } - .onAppear { - searchUserList() - } - .safeAreaInset(edge: .bottom, alignment: .trailing) { + .safeAreaInset(edge: .bottom, alignment: .leading) { HStack { Button(action: { withAnimation { - isEditingFilters = !isEditingFilters + showingHelp = !showingHelp } }) { - Image(systemName: !isEditingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill") + Image(systemName: !editingFilters ? "questionmark.circle" : "questionmark.circle.fill") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + Spacer() + Button(action: { + withAnimation { + editingFilters = !editingFilters + } + }) { + Image(systemName: !editingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill") .padding(.vertical, 5) } .tint(Color(UIColor.secondarySystemBackground)) .foregroundColor(.accentColor) .buttonStyle(.borderedProminent) - } .controlSize(.regular) .padding(5) } .padding(.bottom, 5) + .padding(.bottom, 5) .searchable(text: $searchText, placement: users.count > 10 ? .navigationBarDrawer(displayMode: .always) : .automatic, prompt: "Find a contact") .disableAutocorrection(true) .scrollDismissesKeyboard(.immediately) @@ -277,6 +310,11 @@ struct UserList: View { let isOnlinePredicate = NSPredicate(format: "userNode.lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate) predicates.append(isOnlinePredicate) } + /// Encrypted + if isPkiEncrypted { + let isPkiEncryptedPredicate = NSPredicate(format: "pkiEncrypted == YES") + predicates.append(isPkiEncryptedPredicate) + } /// Favorites if isFavorite { let isFavoritePredicate = NSPredicate(format: "userNode.favorite == YES") diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index ce0eb182..b0ec3a10 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -72,7 +72,9 @@ struct UserMessageList: View { if currentUser && message.receivedACK { // Ack Received if message.realACK { - Text("\(ackErrorVal?.display ?? "Empty Ack Error")").font(.caption2).foregroundColor(.gray) + Text("\(ackErrorVal?.display ?? "Empty Ack Error")") + .font(.caption2) + .foregroundStyle(ackErrorVal?.color ?? Color.secondary) } else { Text("Acknowledged by another node").font(.caption2).foregroundColor(.orange) } @@ -81,7 +83,7 @@ struct UserMessageList: View { Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.yellow) } else if currentUser && message.ackError > 0 { Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) - .font(.caption2).foregroundColor(.red) + .foregroundStyle(ackErrorVal?.color ?? Color.red) } } } diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index f0d2dd04..571511cf 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -93,6 +93,12 @@ struct EnvironmentMetricsLog: View { IndoorAirQuality(iaq: Int(em.iaq), displayMode: IaqDisplayMode.dot ) } } + TableColumn("Wind Speed") { em in + Text("\(String(format: "%.1f", em.windSpeed)) hPa") + } + TableColumn("Wind Direction") { em in + Text("\(String(format: "%.1f", em.windDirection)) hPa") + } TableColumn("timestamp") { em in Text(em.time?.formattedDate(format: dateFormatString) ?? "unknown.age".localized) } diff --git a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift index b3b9c18e..6a717629 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/PositionPopover.swift @@ -14,6 +14,7 @@ struct PositionPopover: View { @ObservedObject var locationsHandler = LocationsHandler.shared @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @Environment(\.dismiss) private var dismiss var position: PositionEntity var popover: Bool = true @@ -52,9 +53,13 @@ struct PositionPopover: View { VStack(alignment: .leading) { /// Time Label { - Text("heard".localized + ":") + if idiom != .phone { + Text("heard".localized + ":") + } LastHeardText(lastHeard: position.time) .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) } icon: { Image(systemName: position.nodePosition?.isOnline ?? false ? "checkmark.circle.fill" : "moon.circle.fill") .symbolRenderingMode(.hierarchical) @@ -67,12 +72,28 @@ struct PositionPopover: View { Text("\(String(format: "%.6f", position.coordinate.latitude)), \(String(format: "%.6f", position.coordinate.longitude))") .textSelection(.enabled) .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) + .allowsTightening(/*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/) } icon: { Image(systemName: "mappin.and.ellipse") .symbolRenderingMode(.hierarchical) .frame(width: 35) } .padding(.bottom, 5) + /// Hops Away + if position.nodePosition?.hopsAway ?? 0 > 0 { + Label { + Text("Hops Away: \(position.nodePosition?.hopsAway ?? 0)") + .textSelection(.enabled) + .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) + } icon: { + Image(systemName: "hare") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + } + .padding(.bottom, 5) + } /// Altitude Label { let formatter = MeasurementFormatter() @@ -81,9 +102,11 @@ struct PositionPopover: View { if Locale.current.measurementSystem == .metric { Text(altitudeFormatter.string(from: distanceInMeters)) .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) } else { Text(altitudeFormatter.string(from: distanceInFeet)) .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) } } icon: { @@ -98,6 +121,7 @@ struct PositionPopover: View { Label { Text("Sats in view: \(String(position.satsInView))") .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) } icon: { Image(systemName: "sparkles") .symbolRenderingMode(.hierarchical) @@ -110,6 +134,7 @@ struct PositionPopover: View { Label { Text("Sequence: \(String(position.seqNo))") .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) } icon: { Image(systemName: "number") .symbolRenderingMode(.hierarchical) @@ -129,11 +154,28 @@ struct PositionPopover: View { .rotationEffect(degrees) } .padding(.bottom, 5) + /// Distance + if let lastLocation = locationsHandler.locationsArray.last { + /// Distance + if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { + let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)) + Label { + Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))") + .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) + } icon: { + Image(systemName: "lines.measurement.horizontal") + .symbolRenderingMode(.hierarchical) + .frame(width: 35) + } + } + } /// Speed let speed = Measurement(value: Double(position.speed), unit: UnitSpeed.kilometersPerHour) Label { Text("Speed: \(speed.formatted(.measurement(width: .abbreviated, numberFormatStyle: .number.precision(.fractionLength(0)))))") .foregroundColor(.primary) + .font(idiom == .phone ? .callout : .body) } icon: { Image(systemName: "gauge.with.dots.needle.33percent") .symbolRenderingMode(.hierarchical) @@ -144,6 +186,7 @@ struct PositionPopover: View { Label { Text("MQTT") + .font(idiom == .phone ? .callout : .body) } icon: { Image(systemName: "network") .symbolRenderingMode(.hierarchical) @@ -152,20 +195,6 @@ struct PositionPopover: View { } .padding(.bottom, 5) } - if let lastLocation = locationsHandler.locationsArray.last { - /// Distance - if lastLocation.distance(from: CLLocation(latitude: LocationsHandler.DefaultLocation.latitude, longitude: LocationsHandler.DefaultLocation.longitude)) > 0.0 { - let metersAway = position.coordinate.distance(from: CLLocationCoordinate2D(latitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude)) - Label { - Text("distance".localized + ": \(distanceFormatter.string(fromDistance: Double(metersAway)))") - .foregroundColor(.primary) - } icon: { - Image(systemName: "lines.measurement.horizontal") - .symbolRenderingMode(.hierarchical) - .frame(width: 35) - } - } - } Spacer() } Spacer() @@ -223,9 +252,9 @@ struct PositionPopover: View { #endif } } - .presentationDetents([.medium, .large]) + .presentationDetents([.fraction(0.65), .large]) .presentationContentInteraction(.scrolls) .presentationDragIndicator(.visible) - .presentationBackgroundInteraction(.enabled(upThrough: .medium)) + .presentationBackgroundInteraction(.enabled(upThrough: .large)) } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 68769280..8a78c08c 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -44,6 +44,24 @@ struct NodeDetail: View { NodeInfoItem(node: node) } Section("Node") { + if let user = node.user { + if !user.keyMatch { + Label { + VStack(alignment: .leading) { + Text("Public Key Mismatch") + .font(.title3) + .foregroundStyle(.red) + Text("The public key does not match the recorded key. You may delete the node and let it exchange keys again, but this may indicate a more serious security problem. Contact the user through another trusted channel, to determine if the key change was due to a factory reset or other intentional action.") + .font(.caption) + .foregroundStyle(.red) + } + } icon: { + Image(systemName: "key.slash.fill") + .symbolRenderingMode(.multicolor) + .foregroundStyle(.red) + } + } + } HStack { Label { Text("Node Number") diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift index 6236f7b7..66d88b28 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListFilter.swift @@ -15,6 +15,7 @@ struct NodeListFilter: View { @Binding var viaLora: Bool @Binding var viaMqtt: Bool @Binding var isOnline: Bool + @Binding var isPkiEncrypted: Bool @Binding var isFavorite: Bool @Binding var isEnvironment: Bool @Binding var distanceFilter: Bool @@ -64,6 +65,19 @@ struct NodeListFilter: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .listRowSeparator(.visible) + Toggle(isOn: $isPkiEncrypted) { + + Label { + Text("Encrypted") + } icon: { + Image(systemName: "lock.fill") + .foregroundColor(.green) + .symbolRenderingMode(.hierarchical) + } + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .listRowSeparator(.visible) + Toggle(isOn: $isFavorite) { Label { @@ -173,9 +187,9 @@ struct NodeListFilter: View { .padding(.bottom) #endif } - .presentationDetents([.medium, .large]) + .presentationDetents([.fraction(0.75), .large]) .presentationContentInteraction(.scrolls) .presentationDragIndicator(.visible) - .presentationBackgroundInteraction(.enabled(upThrough: .medium)) + .presentationBackgroundInteraction(.enabled(upThrough: .large)) } } diff --git a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift index ba7dee6e..63aff338 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeListItem.swift @@ -23,14 +23,16 @@ struct NodeListItem: View { VStack(alignment: .leading) { CircleText(text: node.user?.shortName ?? "?", color: Color(UIColor(hex: UInt32(node.num))), circleSize: 70) .padding(.trailing, 5) - BatteryLevelCompact(node: node, font: .caption, iconFont: .callout, color: .accentColor) - .padding(.trailing, 5) + if node.latestDeviceMetrics != nil { + BatteryCompact(batteryLevel: node.latestDeviceMetrics?.batteryLevel ?? 0, font: .caption, iconFont: .callout, color: .accentColor) + .padding(.trailing, 5) + } } VStack(alignment: .leading) { HStack { Text(node.user?.longName ?? "unknown".localized) - .fontWeight(.medium) .font(.headline) + .allowsTightening(true) if node.favorite { Spacer() Image(systemName: "star.fill") @@ -98,7 +100,7 @@ struct NodeListItem: View { DistanceText(meters: metersAway) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) .foregroundColor(.gray) - let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: CLLocation(latitude: lastPostion.coordinate.latitude, longitude: lastPostion.coordinate.longitude)) + let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord) let headingDegrees = Angle.degrees(trueBearing) Image(systemName: "location.north") .font(.callout) @@ -124,7 +126,7 @@ struct NodeListItem: View { DistanceText(meters: metersAway) .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) .foregroundColor(.secondary) - let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: CLLocation(latitude: lastPostion.coordinate.latitude, longitude: lastPostion.coordinate.longitude)) + let trueBearing = getBearingBetweenTwoPoints(point1: myCoord, point2: nodeCoord) let headingDegrees = Angle.degrees(trueBearing) Image(systemName: "location.north") .font(.callout) @@ -145,7 +147,6 @@ struct NodeListItem: View { HStack { Image(systemName: "\(node.channel).circle.fill") .font(.title2) - .symbolRenderingMode(.multicolor) .frame(width: 30) Text("Channel") .foregroundColor(.secondary) @@ -216,7 +217,6 @@ struct NodeListItem: View { .font(UIDevice.current.userInterfaceIdiom == .phone ? .callout : .caption) Image(systemName: "\(node.hopsAway).square") .font(.title2) - .symbolRenderingMode(.multicolor) } } else { if node.snr != 0 && !node.viaMqtt { diff --git a/Meshtastic/Views/Nodes/MeshMap.swift b/Meshtastic/Views/Nodes/MeshMap.swift index 2dc8d105..9e09a0c0 100644 --- a/Meshtastic/Views/Nodes/MeshMap.swift +++ b/Meshtastic/Views/Nodes/MeshMap.swift @@ -33,13 +33,27 @@ struct MeshMap: View { @Namespace var mapScope @State var mapStyle: MapStyle = MapStyle.standard(elevation: .flat, emphasis: MapStyle.StandardEmphasis.muted, pointsOfInterest: .excludingAll, showsTraffic: false) @State var position = MapCameraPosition.automatic - @State var isEditingSettings = false + @State private var editingSettings = false + @State private var editingFilters = false @State var selectedPosition: PositionEntity? @State var editingWaypoint: WaypointEntity? @State var selectedWaypoint: WaypointEntity? @State var selectedWaypointId: String? @State var newWaypointCoord: CLLocationCoordinate2D? @State var isMeshMap = true + /// Filter + @State private var searchText = "" + @State private var viaLora = true + @State private var viaMqtt = true + @State private var isOnline = false + @State private var isPkiEncrypted = false + @State private var isFavorite = false + @State private var isEnvironment = false + @State private var distanceFilter = false + @State private var maxDistance: Double = 800000 + @State private var hopsAway: Double = -1.0 + @State private var roleFilter = false + @State private var deviceRoles: Set = [] var body: some View { @@ -48,7 +62,6 @@ struct MeshMap: View { MapReader { reader in Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { MeshMapContent(showUserLocation: $showUserLocation, showTraffic: $showTraffic, showPointsOfInterest: $showPointsOfInterest, selectedMapLayer: $selectedMapLayer, selectedPosition: $selectedPosition, selectedWaypoint: $selectedWaypoint) - } .mapScope(mapScope) .mapStyle(mapStyle) @@ -106,7 +119,7 @@ struct MeshMap: View { WaypointForm(waypoint: selection, editMode: true) .padding() } - .sheet(isPresented: $isEditingSettings) { + .sheet(isPresented: $editingSettings) { MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap) } .onChange(of: router.navigationState) { @@ -128,19 +141,46 @@ struct MeshMap: View { return } } + .sheet(isPresented: $editingFilters) { + NodeListFilter( + viaLora: $viaLora, + viaMqtt: $viaMqtt, + isOnline: $isOnline, + isPkiEncrypted: $isPkiEncrypted, + isFavorite: $isFavorite, + isEnvironment: $isEnvironment, + distanceFilter: $distanceFilter, + maximumDistance: $maxDistance, + hopsAway: $hopsAway, + roleFilter: $roleFilter, + deviceRoles: $deviceRoles + ) + } .safeAreaInset(edge: .bottom, alignment: .trailing) { HStack { + Spacer() Button(action: { withAnimation { - isEditingSettings = !isEditingSettings + editingSettings = !editingSettings } }) { - Image(systemName: isEditingSettings ? "info.circle.fill" : "info.circle") + Image(systemName: editingSettings ? "info.circle.fill" : "info.circle") .padding(.vertical, 5) } .tint(Color(UIColor.secondarySystemBackground)) .foregroundColor(.accentColor) .buttonStyle(.borderedProminent) +// Button(action: { +// withAnimation { +// editingFilters = !editingFilters +// } +// }) { +// Image(systemName: !editingFilters ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill") +// .padding(.vertical, 5) +// } +// .tint(Color(UIColor.secondarySystemBackground)) +// .foregroundColor(.accentColor) +// .buttonStyle(.borderedProminent) } .controlSize(.regular) .padding(5) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index c442b315..e252aa95 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -24,6 +24,7 @@ struct NodeList: View { @State private var viaLora = true @State private var viaMqtt = true @State private var isOnline = false + @State private var isPkiEncrypted = false @State private var isFavorite = false @State private var isEnvironment = false @State private var distanceFilter = false @@ -37,10 +38,10 @@ struct NodeList: View { @State private var isPresentingDeleteNodeAlert = false @State private var deleteNodeId: Int64 = 0 - var boolFilters: [Bool] {[ - isOnline, isFavorite, + isOnline, + isPkiEncrypted, isEnvironment, distanceFilter, roleFilter @@ -87,8 +88,15 @@ struct NodeList: View { context: context, node: node ) - /// Don't show trace route, position exchange or delete context menu items for the connected node + /// Don't show message, trace route, position exchange or delete context menu items for the connected node if connectedNode.num != node.num { + Button(action: { + if let url = URL(string: "meshtastic:///messages?userNum=\(node.num)") { + UIApplication.shared.open(url) + } + }) { + Label("Message", systemImage: "message") + } Button { let traceRouteSent = bleManager.sendTraceRouteRequest( destNum: node.num, @@ -154,6 +162,7 @@ struct NodeList: View { viaLora: $viaLora, viaMqtt: $viaMqtt, isOnline: $isOnline, + isPkiEncrypted: $isPkiEncrypted, isFavorite: $isFavorite, isEnvironment: $isEnvironment, distanceFilter: $distanceFilter, @@ -300,7 +309,7 @@ struct NodeList: View { await searchNodeList() } } - .onChange(of: boolFilters) { _ in + .onChange(of: [boolFilters]) { _ in Task { await searchNodeList() } @@ -335,7 +344,7 @@ struct NodeList: View { self.selectedNode = nil } } - .onAppear { + .onFirstAppear { Task { await searchNodeList() } @@ -384,6 +393,11 @@ struct NodeList: View { let isOnlinePredicate = NSPredicate(format: "lastHeard >= %@", Calendar.current.date(byAdding: .minute, value: -15, to: Date())! as NSDate) predicates.append(isOnlinePredicate) } + /// Encrypted + if isPkiEncrypted { + let isPkiEncryptedPredicate = NSPredicate(format: "user.pkiEncrypted == YES") + predicates.append(isPkiEncryptedPredicate) + } /// Favorites if isFavorite { let isFavoritePredicate = NSPredicate(format: "favorite == YES") diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index f9f46cbc..ef3bc384 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -13,6 +13,7 @@ struct AppSettings: View { @State private var isPresentingCoreDataResetConfirm = false @State private var isPresentingDeleteMapTilesConfirm = false @AppStorage("environmentEnableWeatherKit") private var environmentEnableWeatherKit: Bool = true + @AppStorage("enableAdministration") private var enableAdministration: Bool = false var body: some View { VStack { Form { @@ -23,6 +24,10 @@ struct AppSettings: View { UIApplication.shared.open(url) } } + Toggle(isOn: $enableAdministration) { + Label("Administration", systemImage: "gearshape.2") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } Section(header: Text("environment")) { VStack(alignment: .leading) { diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index b43813c2..75132df1 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -107,7 +107,6 @@ struct BluetoothConfig: View { } ) .onAppear { - setBluetoothValues() // Need to request a BluetoothConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node, node.bluetoothConfig == nil { Logger.mesh.info("empty bluetooth config") @@ -117,25 +116,17 @@ struct BluetoothConfig: View { } } } - .onChange(of: enabled) { newEnabled in - if node != nil && node!.bluetoothConfig != nil { - if newEnabled != node!.bluetoothConfig!.enabled { hasChanges = true } - } + .onChange(of: enabled) { + if $0 != node?.bluetoothConfig?.enabled { hasChanges = true } } - .onChange(of: mode) { newMode in - if node != nil && node!.bluetoothConfig != nil { - if newMode != node!.bluetoothConfig!.mode { hasChanges = true } - } + .onChange(of: mode) { + if $0 != node?.bluetoothConfig?.mode ?? -1 { hasChanges = true } } .onChange(of: fixedPin) { newFixedPin in - if node != nil && node!.bluetoothConfig != nil { - if newFixedPin != String(node!.bluetoothConfig!.fixedPin) { hasChanges = true } - } + if newFixedPin != String(node?.bluetoothConfig?.fixedPin ?? -1) { hasChanges = true } } - .onChange(of: deviceLoggingEnabled) { newDeviceLogging in - if node != nil && node!.bluetoothConfig != nil { - if newDeviceLogging != node!.bluetoothConfig!.deviceLoggingEnabled { hasChanges = true } - } + .onChange(of: deviceLoggingEnabled) { + if $0 != node?.bluetoothConfig?.deviceLoggingEnabled { hasChanges = true } } } func setBluetoothValues() { diff --git a/Meshtastic/Views/Settings/Config/ConfigHeader.swift b/Meshtastic/Views/Settings/Config/ConfigHeader.swift index 3ff815f8..3f59a01a 100644 --- a/Meshtastic/Views/Settings/Config/ConfigHeader.swift +++ b/Meshtastic/Views/Settings/Config/ConfigHeader.swift @@ -23,11 +23,12 @@ struct ConfigHeader: View { .foregroundColor(.orange) } else { Text("Remote administration for: \(node?.user?.longName ?? "Unknown")") + .onFirstAppear(onAppear) .font(.title3) - .onAppear(perform: onAppear) } } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? -1 { Text("Configuration for: \(node?.user?.longName ?? "Unknown")") + .onFirstAppear(onAppear) } else { Text("Please connect to a radio to configure settings.") .font(.callout) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index b7f98e36..13d3aa83 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -155,7 +155,7 @@ struct DeviceConfig: View { .disabled(node?.user == nil) .buttonStyle(.bordered) .buttonBorderShape(.capsule) - .controlSize(.large) + .controlSize(.regular) .padding(.leading) .confirmationDialog( "are.you.sure", @@ -180,10 +180,10 @@ struct DeviceConfig: View { .disabled(node?.user == nil) .buttonStyle(.bordered) .buttonBorderShape(.capsule) - .controlSize(.large) + .controlSize(.regular) .padding(.trailing) .confirmationDialog( - "All device and app data will be deleted. You will also need to forget your devices under Settings > Bluetooth.", + "All device and app data will be deleted.", isPresented: $isPresentingFactoryResetConfirm, titleVisibility: .visible ) { @@ -233,13 +233,17 @@ struct DeviceConfig: View { Spacer() } .navigationTitle("device.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setDeviceValues() - // Need to request a LoRaConfig from the remote node before allowing changes + // Need to request a DeviceConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.deviceConfig == nil { Logger.mesh.info("empty device config") let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) @@ -248,55 +252,35 @@ struct DeviceConfig: View { } } } - .onChange(of: deviceRole) { newRole in - if node != nil && node?.deviceConfig != nil { - if newRole != node!.deviceConfig!.role { hasChanges = true } - } + .onChange(of: deviceRole) { + if $0 != node?.deviceConfig?.role ?? -1 { hasChanges = true } } - .onChange(of: serialEnabled) { newSerial in - if node != nil && node?.deviceConfig != nil { - if newSerial != node!.deviceConfig!.serialEnabled { hasChanges = true } - } + .onChange(of: serialEnabled) { + if $0 != node?.deviceConfig?.serialEnabled { hasChanges = true } } - .onChange(of: debugLogEnabled) { newDebugLog in - if node != nil && node?.deviceConfig != nil { - if newDebugLog != node!.deviceConfig!.debugLogEnabled { hasChanges = true } - } + .onChange(of: debugLogEnabled) { + if $0 != node?.deviceConfig?.debugLogEnabled { hasChanges = true } } .onChange(of: buttonGPIO) { newButtonGPIO in - if node != nil && node?.deviceConfig != nil { - if newButtonGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true } - } + if newButtonGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true } } .onChange(of: buzzerGPIO) { newBuzzerGPIO in - if node != nil && node?.deviceConfig != nil { - if newBuzzerGPIO != node!.deviceConfig!.buttonGpio { hasChanges = true } - } + if newBuzzerGPIO != node?.deviceConfig?.buttonGpio ?? -1 { hasChanges = true } } .onChange(of: rebroadcastMode) { newRebroadcastMode in - if node != nil && node?.deviceConfig != nil { - if newRebroadcastMode != node!.deviceConfig!.rebroadcastMode { hasChanges = true } - } + if newRebroadcastMode != node?.deviceConfig?.rebroadcastMode ?? -1 { hasChanges = true } } .onChange(of: nodeInfoBroadcastSecs) { newNodeInfoBroadcastSecs in - if node != nil && node?.deviceConfig != nil { - if newNodeInfoBroadcastSecs != node!.deviceConfig!.nodeInfoBroadcastSecs { hasChanges = true } - } + if newNodeInfoBroadcastSecs != node?.deviceConfig?.nodeInfoBroadcastSecs ?? -1 { hasChanges = true } } - .onChange(of: doubleTapAsButtonPress) { newDoubleTapAsButtonPress in - if node != nil && node?.deviceConfig != nil { - if newDoubleTapAsButtonPress != node!.deviceConfig!.doubleTapAsButtonPress { hasChanges = true } - } + .onChange(of: doubleTapAsButtonPress) { + if $0 != node?.deviceConfig?.doubleTapAsButtonPress { hasChanges = true } } - .onChange(of: isManaged) { newIsManaged in - if node != nil && node?.deviceConfig != nil { - if newIsManaged != node!.deviceConfig!.isManaged { hasChanges = true } - } + .onChange(of: isManaged) { + if $0 != node?.deviceConfig?.isManaged { hasChanges = true } } .onChange(of: tzdef) { newTzdef in - if node != nil && node?.deviceConfig != nil { - if newTzdef != node!.deviceConfig!.tzdef { hasChanges = true } - } + if newTzdef != node?.deviceConfig?.tzdef { hasChanges = true } } .onChange(of: ledHeartbeatEnabled) { newLedHeartbeatEnabled in if node != nil && node?.deviceConfig != nil { diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index f800ebb6..1f3f6de4 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -154,13 +154,16 @@ struct DisplayConfig: View { } .navigationTitle("display.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setDisplayValues() - // Need to request a LoRaConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.displayConfig == nil { Logger.mesh.info("empty display config") @@ -171,49 +174,31 @@ struct DisplayConfig: View { } } .onChange(of: screenOnSeconds) { newScreenSecs in - if node != nil && node!.displayConfig != nil { - if newScreenSecs != node!.displayConfig!.screenOnSeconds { hasChanges = true } - } + if newScreenSecs != node?.displayConfig?.screenOnSeconds ?? -1 { hasChanges = true } } .onChange(of: screenCarouselInterval) { newCarouselSecs in - if node != nil && node!.displayConfig != nil { - if newCarouselSecs != node!.displayConfig!.screenCarouselInterval { hasChanges = true } - } + if newCarouselSecs != node?.displayConfig?.screenCarouselInterval ?? -1 { hasChanges = true } } - .onChange(of: compassNorthTop) { newCompassNorthTop in - if node != nil && node!.displayConfig != nil { - if newCompassNorthTop != node!.displayConfig!.compassNorthTop { hasChanges = true } - } + .onChange(of: compassNorthTop) { + if $0 != node?.displayConfig?.compassNorthTop { hasChanges = true } } - .onChange(of: wakeOnTapOrMotion) { newWakeOnTapOrMotion in - if node != nil && node!.displayConfig != nil { - if newWakeOnTapOrMotion != node!.displayConfig!.wakeOnTapOrMotion { hasChanges = true } - } + .onChange(of: wakeOnTapOrMotion) { + if $0 != node?.displayConfig?.wakeOnTapOrMotion { hasChanges = true } } .onChange(of: gpsFormat) { newGpsFormat in - if node != nil && node!.displayConfig != nil { - if newGpsFormat != node!.displayConfig!.gpsFormat { hasChanges = true } - } + if newGpsFormat != node?.displayConfig?.gpsFormat ?? -1 { hasChanges = true } } - .onChange(of: flipScreen) { newFlipScreen in - if node != nil && node!.displayConfig != nil { - if newFlipScreen != node!.displayConfig!.flipScreen { hasChanges = true } - } + .onChange(of: flipScreen) { + if $0 != node?.displayConfig?.flipScreen { hasChanges = true } } .onChange(of: oledType) { newOledType in - if node != nil && node!.displayConfig != nil { - if newOledType != node!.displayConfig!.oledType { hasChanges = true } - } + if newOledType != node?.displayConfig?.oledType ?? -1 { hasChanges = true } } .onChange(of: displayMode) { newDisplayMode in - if node != nil && node!.displayConfig != nil { - if newDisplayMode != node!.displayConfig!.displayMode { hasChanges = true } - } + if newDisplayMode != node?.displayConfig?.displayMode ?? -1 { hasChanges = true } } .onChange(of: units) { newUnits in - if node != nil && node!.displayConfig != nil { - if newUnits != node!.displayConfig!.units { hasChanges = true } - } + if newUnits != node?.displayConfig?.units ?? -1 { hasChanges = true } } } func setDisplayValues() { diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 8239282c..dad7c1a4 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -223,12 +223,16 @@ struct LoRaConfig: View { } } .navigationTitle("lora.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setLoRaValues() // Need to request a LoRaConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.loRaConfig == nil { Logger.mesh.info("empty lora config") @@ -239,69 +243,43 @@ struct LoRaConfig: View { } } .onChange(of: region) { newRegion in - if node != nil && node!.loRaConfig != nil { - if newRegion != node!.loRaConfig!.regionCode { hasChanges = true } - } + if newRegion != node?.loRaConfig?.regionCode ?? -1 { hasChanges = true } } - .onChange(of: usePreset) { newUsePreset in - if node != nil && node!.loRaConfig != nil { - if newUsePreset != node!.loRaConfig!.usePreset { hasChanges = true } - } + .onChange(of: usePreset) { + if $0 != node?.loRaConfig?.usePreset { hasChanges = true } } .onChange(of: modemPreset) { newModemPreset in - if node != nil && node!.loRaConfig != nil { - if newModemPreset != node!.loRaConfig!.modemPreset { hasChanges = true } - } + if newModemPreset != node?.loRaConfig?.modemPreset ?? -1 { hasChanges = true } } .onChange(of: hopLimit) { newHopLimit in - if node != nil && node!.loRaConfig != nil { - if newHopLimit != node!.loRaConfig!.hopLimit { hasChanges = true } - } + if newHopLimit != node?.loRaConfig?.hopLimit ?? -1 { hasChanges = true } } .onChange(of: channelNum) { newChannelNum in - if node != nil && node!.loRaConfig != nil { - if newChannelNum != node!.loRaConfig!.channelNum { hasChanges = true } - } + if newChannelNum != node?.loRaConfig?.channelNum ?? -1 { hasChanges = true } } .onChange(of: bandwidth) { newBandwidth in - if node != nil && node!.loRaConfig != nil { - if newBandwidth != node!.loRaConfig!.bandwidth { hasChanges = true } - } + if newBandwidth != node?.loRaConfig?.bandwidth ?? -1 { hasChanges = true } } .onChange(of: codingRate) { newCodingRate in - if node != nil && node!.loRaConfig != nil { - if newCodingRate != node!.loRaConfig!.codingRate { hasChanges = true } - } + if newCodingRate != node?.loRaConfig?.codingRate ?? -1 { hasChanges = true } } .onChange(of: spreadFactor) { newSpreadFactor in - if node != nil && node!.loRaConfig != nil { - if newSpreadFactor != node!.loRaConfig!.spreadFactor { hasChanges = true } - } + if newSpreadFactor != node?.loRaConfig?.spreadFactor ?? -1 { hasChanges = true } } - .onChange(of: rxBoostedGain) { newRxBoostedGain in - if node != nil && node!.loRaConfig != nil { - if newRxBoostedGain != node!.loRaConfig!.sx126xRxBoostedGain { hasChanges = true } - } + .onChange(of: rxBoostedGain) { + if $0 != node?.loRaConfig?.sx126xRxBoostedGain { hasChanges = true } } .onChange(of: overrideFrequency) { newOverrideFrequency in - if node != nil && node!.loRaConfig != nil { - if newOverrideFrequency != node!.loRaConfig!.overrideFrequency { hasChanges = true } - } + if newOverrideFrequency != node?.loRaConfig?.overrideFrequency { hasChanges = true } } .onChange(of: txPower) { newTxPower in - if node != nil && node!.loRaConfig != nil { - if newTxPower != node!.loRaConfig!.txPower { hasChanges = true } - } + if newTxPower != node?.loRaConfig?.txPower ?? -1 { hasChanges = true } } - .onChange(of: txEnabled) { newTxEnabled in - if node != nil && node!.loRaConfig != nil { - if newTxEnabled != node!.loRaConfig!.txEnabled { hasChanges = true } - } + .onChange(of: txEnabled) { + if $0 != node?.loRaConfig?.txEnabled { hasChanges = true } } - .onChange(of: ignoreMqtt) { newIgnoreMqtt in - if node != nil && node!.loRaConfig != nil { - if newIgnoreMqtt != node!.loRaConfig!.ignoreMqtt { hasChanges = true } - } + .onChange(of: ignoreMqtt) { + if $0 != node?.loRaConfig?.ignoreMqtt { hasChanges = true } } } func setLoRaValues() { diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index 3fe5560d..76c98752 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -50,10 +50,6 @@ struct AmbientLightingConfig: View { Stepper("Current: \(current)", value: $current, in: 0...31, step: 1) .padding(5) } - .onChange(of: color, initial: true) { - components = color.resolve(in: environment) - hasChanges = true - } } } .disabled(self.bleManager.connectedPeripheral == nil || node?.ambientLightingConfig == nil) @@ -80,12 +76,16 @@ struct AmbientLightingConfig: View { } } .navigationTitle("ambient.lighting.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setAmbientLightingConfigValue() // Need to request a Ambient Lighting Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.ambientLightingConfig == nil { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) @@ -94,9 +94,19 @@ struct AmbientLightingConfig: View { } } } - .onChange(of: ledState) { newLedState in - if node != nil && node!.ambientLightingConfig != nil { - if newLedState != node!.ambientLightingConfig!.ledState { hasChanges = true } + .onChange(of: ledState) { + if let val = node?.ambientLightingConfig?.ledState { + hasChanges = $0 != val + } + } + .onChange(of: current) { + if let val = node?.ambientLightingConfig?.current { + hasChanges = $0 != val + } + } + .onChange(of: color) { c in + if color != c { + hasChanges = true } } } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 670e24e1..b4ec0b63 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -224,12 +224,16 @@ struct CannedMessagesConfig: View { } } .navigationTitle("canned.messages.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setCannedMessagesValues() // Need to request a CannedMessagesModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.cannedMessageConfig == nil { Logger.mesh.info("empty canned messages module config") @@ -268,24 +272,24 @@ struct CannedMessagesConfig: View { hasChanges = true } - .onChange(of: enabled) { newEnabled in - if node != nil && node!.cannedMessageConfig != nil { - if newEnabled != node!.cannedMessageConfig!.enabled { hasChanges = true } + .onChange(of: enabled) { + if let val = node?.cannedMessageConfig?.enabled { + hasChanges = $0 != val } } - .onChange(of: sendBell) { newBell in - if node != nil && node!.cannedMessageConfig != nil { - if newBell != node!.cannedMessageConfig!.sendBell { hasChanges = true } + .onChange(of: sendBell) { + if let val = node?.cannedMessageConfig?.sendBell { + hasChanges = $0 != val } } - .onChange(of: rotary1Enabled) { newRot1 in - if node != nil && node!.cannedMessageConfig != nil { - if newRot1 != node!.cannedMessageConfig!.rotary1Enabled { hasChanges = true } + .onChange(of: rotary1Enabled) { + if let val = node?.cannedMessageConfig?.rotary1Enabled { + hasChanges = $0 != val } } - .onChange(of: updown1Enabled) { newUpDown in - if node != nil && node!.cannedMessageConfig != nil { - if newUpDown != node!.cannedMessageConfig!.updown1Enabled { hasChanges = true } + .onChange(of: updown1Enabled) { + if let val = node?.cannedMessageConfig?.updown1Enabled { + hasChanges = $0 != val } } .onChange(of: inputbrokerPinA) { newPinA in diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 480c4e24..1ff1ed86 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -180,12 +180,16 @@ struct DetectionSensorConfig: View { } } .navigationTitle("detection.sensor.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setDetectionSensorValues() // Need to request a Detection Sensor Module Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil { Logger.mesh.info("empty detection sensor module config") @@ -195,14 +199,14 @@ struct DetectionSensorConfig: View { } } } - .onChange(of: enabled) { newEnabled in - if node != nil && node?.detectionSensorConfig != nil { - if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true } + .onChange(of: enabled) { + if let val = node?.detectionSensorConfig?.enabled { + hasChanges = $0 != val } } - .onChange(of: sendBell) { newSendBell in - if node != nil && node?.detectionSensorConfig != nil { - if newSendBell != node!.detectionSensorConfig!.sendBell { hasChanges = true } + .onChange(of: sendBell) { + if let val = node?.detectionSensorConfig?.sendBell { + hasChanges = $0 != val } } .onChange(of: detectionTriggeredHigh) { newDetectionTriggeredHigh in @@ -210,9 +214,9 @@ struct DetectionSensorConfig: View { if newDetectionTriggeredHigh != node!.detectionSensorConfig!.detectionTriggeredHigh { hasChanges = true } } } - .onChange(of: usePullup) { newUsePullup in - if node != nil && node?.detectionSensorConfig != nil { - if newUsePullup != node!.detectionSensorConfig!.usePullup { hasChanges = true } + .onChange(of: usePullup) { + if let val = node?.detectionSensorConfig?.usePullup { + hasChanges = $0 != val } } .onChange(of: name) { newName in diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index abdca8a2..32cacbd3 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -190,12 +190,16 @@ struct ExternalNotificationConfig: View { } } .navigationTitle("external.notification.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setExternalNotificationValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.externalNotificationConfig == nil { Logger.mesh.info("empty external notification module config") @@ -205,44 +209,44 @@ struct ExternalNotificationConfig: View { } } } - .onChange(of: enabled) { newEnabled in - if node != nil && node!.externalNotificationConfig != nil { - if newEnabled != node!.externalNotificationConfig!.enabled { hasChanges = true } + .onChange(of: enabled) { + if let val = node?.externalNotificationConfig?.enabled { + hasChanges = $0 != val } } - .onChange(of: alertBell) { newAlertBell in - if node != nil && node!.externalNotificationConfig != nil { - if newAlertBell != node!.externalNotificationConfig!.alertBell { hasChanges = true } + .onChange(of: alertBell) { + if let val = node?.externalNotificationConfig?.alertBell { + hasChanges = $0 != val } } - .onChange(of: alertBellBuzzer) { newAlertBellBuzzer in - if node != nil && node!.externalNotificationConfig != nil { - if newAlertBellBuzzer != node!.externalNotificationConfig!.alertBellBuzzer { hasChanges = true } + .onChange(of: alertBellBuzzer) { + if let val = node?.externalNotificationConfig?.alertBellBuzzer { + hasChanges = $0 != val } } - .onChange(of: alertBellVibra) { newAlertBellVibra in - if node != nil && node!.externalNotificationConfig != nil { - if newAlertBellVibra != node!.externalNotificationConfig!.alertBellVibra { hasChanges = true } + .onChange(of: alertBellVibra) { + if let val = node?.externalNotificationConfig?.alertBellVibra { + hasChanges = $0 != val } } - .onChange(of: alertMessage) { newAlertMessage in - if node != nil && node!.externalNotificationConfig != nil { - if newAlertMessage != node!.externalNotificationConfig!.alertMessage { hasChanges = true } + .onChange(of: alertMessage) { + if let val = node?.externalNotificationConfig?.alertMessage { + hasChanges = $0 != val } } - .onChange(of: alertMessageBuzzer) { newAlertMessageBuzzer in - if node != nil && node!.externalNotificationConfig != nil { - if newAlertMessageBuzzer != node!.externalNotificationConfig!.alertMessageBuzzer { hasChanges = true } + .onChange(of: alertMessageBuzzer) { + if let val = node?.externalNotificationConfig?.alertMessageBuzzer { + hasChanges = $0 != val } } - .onChange(of: alertMessageVibra) { newAlertMessageVibra in - if node != nil && node!.externalNotificationConfig != nil { - if newAlertMessageVibra != node!.externalNotificationConfig!.alertMessageVibra { hasChanges = true } + .onChange(of: alertMessageVibra) { + if let val = node?.externalNotificationConfig?.alertMessageVibra { + hasChanges = $0 != val } } - .onChange(of: active) { newActive in - if node != nil && node!.externalNotificationConfig != nil { - if newActive != node!.externalNotificationConfig!.active { hasChanges = true } + .onChange(of: active) { + if let val = node?.externalNotificationConfig?.active { + hasChanges = $0 != val } } .onChange(of: output) { newOutput in @@ -265,9 +269,9 @@ struct ExternalNotificationConfig: View { if newOutputMs != node!.externalNotificationConfig!.outputMilliseconds { hasChanges = true } } } - .onChange(of: usePWM) { newUsePWM in - if node != nil && node!.externalNotificationConfig != nil { - if newUsePWM != node!.externalNotificationConfig!.usePWM { hasChanges = true } + .onChange(of: usePWM) { + if let val = node?.externalNotificationConfig?.usePWM { + hasChanges = $0 != val } } .onChange(of: nagTimeout) { newNagTimeout in @@ -275,9 +279,9 @@ struct ExternalNotificationConfig: View { if newNagTimeout != node!.externalNotificationConfig!.nagTimeout { hasChanges = true } } } - .onChange(of: useI2SAsBuzzer) { newUseI2SAsBuzzer in - if node != nil && node!.externalNotificationConfig != nil { - if newUseI2SAsBuzzer != node!.externalNotificationConfig!.useI2SAsBuzzer { hasChanges = true } + .onChange(of: useI2SAsBuzzer) { + if let val = node?.externalNotificationConfig?.useI2SAsBuzzer { + hasChanges = $0 != val } } } diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index f923e560..d6176526 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -271,10 +271,27 @@ struct MQTTConfig: View { } } .navigationTitle("mqtt.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?", mqttProxyConnected: bleManager.mqttProxyConnected) - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) + .onChange(of: enabled) { + if $0 != node?.mqttConfig?.enabled { hasChanges = true } + } + .onChange(of: proxyToClientEnabled) { newProxyToClientEnabled in + if newProxyToClientEnabled { + jsonEnabled = false + } + if newProxyToClientEnabled != node?.mqttConfig?.proxyToClientEnabled { hasChanges = true } + if newProxyToClientEnabled { + jsonEnabled = false + } + } .onChange(of: address) { newAddress in if node != nil && node?.mqttConfig != nil { if newAddress != node!.mqttConfig!.address { hasChanges = true } @@ -298,39 +315,17 @@ struct MQTTConfig: View { .onChange(of: selectedTopic) { newSelectedTopic in root = newSelectedTopic } - .onChange(of: enabled) { newEnabled in - if node != nil && node?.mqttConfig != nil { - if newEnabled != node!.mqttConfig!.enabled { hasChanges = true } - } - } - .onChange(of: proxyToClientEnabled) { newProxyToClientEnabled in - if newProxyToClientEnabled { - jsonEnabled = false - } - if node != nil && node?.mqttConfig != nil { - if newProxyToClientEnabled != node!.mqttConfig!.proxyToClientEnabled { hasChanges = true } - if newProxyToClientEnabled { - jsonEnabled = false - } - } - } - .onChange(of: encryptionEnabled) { newEncryptionEnabled in - if node != nil && node?.mqttConfig != nil { - if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true } - } + .onChange(of: encryptionEnabled) { + if $0 != node?.mqttConfig?.encryptionEnabled { hasChanges = true } } .onChange(of: jsonEnabled) { newJsonEnabled in if newJsonEnabled { proxyToClientEnabled = false } - if node != nil && node?.mqttConfig != nil { - if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true } - } + if newJsonEnabled != node?.mqttConfig?.jsonEnabled { hasChanges = true } } - .onChange(of: tlsEnabled) { newTlsEnabled in - if node != nil && node?.mqttConfig != nil { - if newTlsEnabled != node!.mqttConfig!.tlsEnabled { hasChanges = true } - } + .onChange(of: tlsEnabled) { + if $0 != node?.mqttConfig?.tlsEnabled { hasChanges = true } } .onChange(of: mqttConnected) { newMqttConnected in if newMqttConnected == false { @@ -343,13 +338,8 @@ struct MQTTConfig: View { } } } - .onChange(of: mapReportingEnabled) { newMapReportingEnabled in - if node != nil && node?.mqttConfig != nil { - if newMapReportingEnabled != node!.mqttConfig!.mapReportingEnabled { hasChanges = true } - } - } - .onChange(of: preciseLocation) { _ in - hasChanges = true + .onChange(of: mapReportingEnabled) { + if $0 != node?.mqttConfig?.mapReportingEnabled { hasChanges = true } } .onChange(of: mapPublishIntervalSecs) { newMapPublishIntervalSecs in if node != nil && node?.mqttConfig != nil { @@ -357,7 +347,6 @@ struct MQTTConfig: View { } } .onAppear { - setMqttValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.mqttConfig == nil { Logger.mesh.info("empty mqtt module config") diff --git a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift index d7670a7c..6e7bef08 100644 --- a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift @@ -58,7 +58,6 @@ struct PaxCounterConfig: View { ) }) .onAppear { - setPaxValues() // Need to request a PAX Counter module config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.paxCounterConfig == nil { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) @@ -68,14 +67,10 @@ struct PaxCounterConfig: View { } } .onChange(of: enabled) { - if let val = node?.paxCounterConfig?.enabled { - hasChanges = $0 != val - } + if $0 != node?.paxCounterConfig?.enabled { hasChanges = true } } .onChange(of: paxcounterUpdateInterval) { - if let val = node?.paxCounterConfig?.updateInterval { - hasChanges = $0 != val - } + if $0 != node?.paxCounterConfig?.updateInterval ?? -1 { hasChanges = true } } SaveConfigButton(node: node, hasChanges: $hasChanges) { diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 0bf99d65..001ad2b1 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -72,12 +72,16 @@ struct RangeTestConfig: View { } } .navigationTitle("range.test.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setRangeTestValues() // Need to request a RangeTestModule Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.rangeTestConfig == nil { Logger.mesh.debug("empty range test module config") @@ -87,20 +91,14 @@ struct RangeTestConfig: View { } } } - .onChange(of: enabled) { newEnabled in - if node != nil && node!.rangeTestConfig != nil { - if newEnabled != node!.rangeTestConfig!.enabled { hasChanges = true } - } + .onChange(of: enabled) { + if $0 != node?.rangeTestConfig?.enabled { hasChanges = true } } - .onChange(of: save) { newSave in - if node != nil && node!.rangeTestConfig != nil { - if newSave != node!.rangeTestConfig!.save { hasChanges = true } - } + .onChange(of: save) { + if $0 != node?.rangeTestConfig?.save { hasChanges = true } } - .onChange(of: sender) { newSender in - if node != nil && node!.rangeTestConfig != nil { - if newSender != node!.rangeTestConfig!.sender { hasChanges = true } - } + .onChange(of: sender) { + if $0 != node?.rangeTestConfig?.sender ?? -1 { hasChanges = true } } } } diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index 3128fffe..95f237a3 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -62,12 +62,16 @@ struct RtttlConfig: View { } } .navigationTitle("config.ringtone.title") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setRtttLConfigValue() // Need to request a Rtttl Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && (node?.rtttlConfig == nil || node?.rtttlConfig?.ringtone?.count ?? 0 == 0) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 813e1328..c7243a87 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -127,13 +127,16 @@ struct SerialConfig: View { } } .navigationTitle("serial.config") - .navigationBarItems(trailing: - - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setSerialValues() // Need to request a SerialModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.serialConfig == nil { Logger.mesh.debug("empty serial module config") @@ -142,61 +145,40 @@ struct SerialConfig: View { _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } } - } - .onChange(of: enabled) { newEnabled in - - if node != nil && node!.serialConfig != nil { - - if newEnabled != node!.serialConfig!.enabled { hasChanges = true } - } + .onChange(of: enabled) { + if $0 != node?.serialConfig?.enabled { hasChanges = true } } - .onChange(of: echo) { newEcho in - - if node != nil && node!.serialConfig != nil { - - if newEcho != node!.serialConfig!.echo { hasChanges = true } - } + .onChange(of: echo) { + if $0 != node?.serialConfig?.echo { hasChanges = true } } .onChange(of: rxd) { newRxd in - if node != nil && node!.serialConfig != nil { - if newRxd != node!.serialConfig!.rxd { hasChanges = true } } } .onChange(of: txd) { newTxd in - if node != nil && node!.serialConfig != nil { - if newTxd != node!.serialConfig!.txd { hasChanges = true } } } .onChange(of: baudRate) { newBaud in - if node != nil && node!.serialConfig != nil { - if newBaud != node!.serialConfig!.baudRate { hasChanges = true } } } .onChange(of: timeout) { newTimeout in - if node != nil && node!.serialConfig != nil { - if newTimeout != node!.serialConfig!.timeout { hasChanges = true } } } .onChange(of: overrideConsoleSerialPort) { newOverrideConsoleSerialPort in - if node != nil && node!.serialConfig != nil { - if newOverrideConsoleSerialPort != node!.serialConfig!.overrideConsoleSerialPort { hasChanges = true } } } .onChange(of: mode) { newMode in - if node != nil && node!.serialConfig != nil { - if newMode != node!.serialConfig!.mode { hasChanges = true } } } diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index e73380f3..4829a5fa 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -137,10 +137,15 @@ struct StoreForwardConfig: View { } } .navigationTitle("storeforward.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { // Need to request a Detection Sensor Module Config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.storeForwardConfig == nil { @@ -150,7 +155,6 @@ struct StoreForwardConfig: View { _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) } } - setStoreAndForwardValues() } .onChange(of: enabled) { newEnabled in if node != nil && node?.storeForwardConfig != nil { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index a1f827b7..df811677 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -125,12 +125,16 @@ struct TelemetryConfig: View { } } .navigationTitle("telemetry.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setTelemetryValues() // Need to request a TelemetryModuleConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.telemetryConfig == nil { Logger.mesh.info("empty telemetry module config") diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index d969eab9..0554c766 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -109,12 +109,16 @@ struct NetworkConfig: View { } } .navigationTitle("network.config") - .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + .navigationBarItems( + trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: bleManager.connectedPeripheral?.shortName ?? "?" + ) + } + ) .onAppear { - setNetworkValues() // Need to request a NetworkConfig from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.networkConfig == nil { Logger.mesh.info("empty network config") @@ -124,30 +128,20 @@ struct NetworkConfig: View { } } } - .onChange(of: wifiEnabled) { newEnabled in - if node != nil && node!.networkConfig != nil { - if newEnabled != node!.networkConfig!.wifiEnabled { hasChanges = true } - } + .onChange(of: wifiEnabled) { + if $0 != node?.networkConfig?.wifiEnabled { hasChanges = true } } .onChange(of: wifiSsid) { newSSID in - if node != nil && node!.networkConfig != nil { - if newSSID != node!.networkConfig!.wifiSsid { hasChanges = true } - } + if newSSID != node?.networkConfig?.wifiSsid { hasChanges = true } } .onChange(of: wifiPsk) { newPsk in - if node != nil && node!.networkConfig != nil { - if newPsk != node!.networkConfig!.wifiPsk { hasChanges = true } - } + if newPsk != node?.networkConfig?.wifiPsk { hasChanges = true } } - .onChange(of: wifiMode) { newMode in - if node != nil && node!.networkConfig != nil { - if newMode != node!.networkConfig!.wifiMode { hasChanges = true } - } + .onChange(of: wifiMode) { + if $0 != node?.networkConfig?.wifiMode ?? -1 { hasChanges = true } } - .onChange(of: ethEnabled) { newEthEnabled in - if node != nil && node!.networkConfig != nil { - if newEthEnabled != node!.networkConfig!.ethEnabled { hasChanges = true } - } + .onChange(of: ethEnabled) { + if $0 != node?.networkConfig?.ethEnabled { hasChanges = true } } } func setNetworkValues() { diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index aa6960a0..0f71b379 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -377,7 +377,6 @@ struct PositionConfig: View { } ) .onAppear { - setPositionValues() supportedVersion = bleManager.connectedVersion == "0.0.0" || self.minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(bleManager.connectedVersion, options: .numeric) == .orderedSame // Need to request a PositionConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, node?.positionConfig == nil { @@ -405,51 +404,39 @@ struct PositionConfig: View { } } } - .onChange(of: gpsMode) { _ in - handleChanges() + .onChange(of: gpsMode) { newGpsMode in + if newGpsMode != node?.positionConfig?.gpsMode ?? 0 { hasChanges = true } } - .onChange(of: rxGpio) { _ in - handleChanges() + .onChange(of: rxGpio) { newRxGpio in + if newRxGpio != node?.positionConfig?.rxGpio ?? 0 { hasChanges = true } } - .onChange(of: txGpio) { _ in - handleChanges() + .onChange(of: txGpio) { newTxGpio in + if newTxGpio != node?.positionConfig?.txGpio ?? 0 { hasChanges = true } } - .onChange(of: gpsEnGpio) { _ in - handleChanges() + .onChange(of: gpsEnGpio) { newGpsEnGpio in + if newGpsEnGpio != node?.positionConfig?.gpsEnGpio ?? 0 { hasChanges = true } } - .onChange(of: smartPositionEnabled) { _ in - handleChanges() + .onChange(of: smartPositionEnabled) { newSmartPositionEnabled in + if newSmartPositionEnabled != node?.positionConfig?.smartPositionEnabled { hasChanges = true } } - .onChange(of: positionBroadcastSeconds) { _ in - handleChanges() + .onChange(of: positionBroadcastSeconds) { newPositionBroadcastSeconds in + if newPositionBroadcastSeconds != node?.positionConfig?.positionBroadcastSeconds ?? 0 { hasChanges = true } } - .onChange(of: broadcastSmartMinimumIntervalSecs) { _ in - handleChanges() + .onChange(of: broadcastSmartMinimumIntervalSecs) { newBroadcastSmartMinimumIntervalSecs in + if newBroadcastSmartMinimumIntervalSecs != node?.positionConfig?.broadcastSmartMinimumIntervalSecs ?? 0 { hasChanges = true } } - .onChange(of: broadcastSmartMinimumDistance) { _ in - handleChanges() + .onChange(of: broadcastSmartMinimumDistance) { newBroadcastSmartMinimumDistance in + if newBroadcastSmartMinimumDistance != node?.positionConfig?.broadcastSmartMinimumDistance ?? 0 { hasChanges = true } } - .onChange(of: gpsUpdateInterval) { _ in - handleChanges() - } - .onChange(of: positionFlags) { _ in - handleChanges() + .onChange(of: gpsUpdateInterval) { newGpsUpdateInterval in + if newGpsUpdateInterval != node?.positionConfig?.gpsUpdateInterval ?? 0 { hasChanges = true } } } - func handleChanges() { + func handlePositionFlagtChanges() { guard let positionConfig = node?.positionConfig else { return } let pf = PositionFlags(rawValue: self.positionFlags) - hasChanges = positionConfig.deviceGpsEnabled != deviceGpsEnabled || - positionConfig.gpsMode != gpsMode || - positionConfig.rxGpio != rxGpio || - positionConfig.txGpio != txGpio || - positionConfig.gpsEnGpio != gpsEnGpio || - positionConfig.smartPositionEnabled != smartPositionEnabled || - positionConfig.positionBroadcastSeconds != positionBroadcastSeconds || - positionConfig.broadcastSmartMinimumIntervalSecs != broadcastSmartMinimumIntervalSecs || - positionConfig.broadcastSmartMinimumDistance != broadcastSmartMinimumDistance || - positionConfig.gpsUpdateInterval != gpsUpdateInterval || + hasChanges = pf.contains(.Altitude) || pf.contains(.AltitudeMsl) || pf.contains(.Satsinview) || diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index d8de7e44..42b6a534 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -119,7 +119,6 @@ struct PowerConfig: View { } .onAppear { Api().loadDeviceHardwareData { (hw) in - for device in hw { let currentHardware = node?.user?.hwModel ?? "UNSET" let deviceString = device.hwModelSlug.replacingOccurrences(of: "_", with: "") @@ -128,8 +127,6 @@ struct PowerConfig: View { } } } - setPowerValues() - // Need to request a Power config from the remote node before allowing changes if bleManager.connectedPeripheral != nil && node?.powerConfig == nil { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) @@ -139,40 +136,30 @@ struct PowerConfig: View { } } .onChange(of: isPowerSaving) { - if let val = node?.powerConfig?.isPowerSaving { - hasChanges = $0 != val - } + if $0 != node?.powerConfig?.isPowerSaving { hasChanges = true } } - .onChange(of: shutdownOnPowerLoss) { _ in - hasChanges = true + .onChange(of: shutdownOnPowerLoss) { newShutdownOnPowerLoss in + if newShutdownOnPowerLoss { + hasChanges = true + } } .onChange(of: shutdownAfterSecs) { - if let val = node?.powerConfig?.onBatteryShutdownAfterSecs { - hasChanges = $0 != val - } + if $0 != node?.powerConfig?.minWakeSecs ?? -1 { hasChanges = true } } .onChange(of: adcOverride) { _ in hasChanges = true } - .onChange(of: adcMultiplier) { - if let val = node?.powerConfig?.adcMultiplierOverride { - hasChanges = $0 != val - } + .onChange(of: adcMultiplier) { newAdcMultiplier in + if newAdcMultiplier != node?.powerConfig?.adcMultiplierOverride ?? -1 { hasChanges = true } } .onChange(of: waitBluetoothSecs) { - if let val = node?.powerConfig?.waitBluetoothSecs { - hasChanges = $0 != val - } + if $0 != node?.powerConfig?.waitBluetoothSecs ?? -1 { hasChanges = true } } .onChange(of: lsSecs) { - if let val = node?.powerConfig?.lsSecs { - hasChanges = $0 != val - } + if $0 != node?.powerConfig?.lsSecs ?? -1 { hasChanges = true } } .onChange(of: minWakeSecs) { - if let val = node?.powerConfig?.minWakeSecs { - hasChanges = $0 != val - } + if $0 != node?.powerConfig?.minWakeSecs ?? -1 { hasChanges = true } } SaveConfigButton(node: node, hasChanges: $hasChanges) { diff --git a/Meshtastic/Views/Settings/Config/SecurityConfig.swift b/Meshtastic/Views/Settings/Config/SecurityConfig.swift new file mode 100644 index 00000000..597324e0 --- /dev/null +++ b/Meshtastic/Views/Settings/Config/SecurityConfig.swift @@ -0,0 +1,221 @@ +// +// Security.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/7/24. +// + +import Foundation +import SwiftUI +import CoreData +import MeshtasticProtobufs +import OSLog + +struct SecurityConfig: View { + + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.dismiss) private var goBack + + var node: NodeInfoEntity? + + @State var hasChanges = false + @State var publicKey = "" + @State var hasValidPublicKey: Bool = false + @State var privateKey = "" + @State var hasValidPrivateKey: Bool = false + @State var adminKey = "" + @State var hasValidAdminKey: Bool = true + @State var isManaged = false + @State var serialEnabled = false + @State var debugLogApiEnabled = false + @State var bluetoothLoggingEnabled = false + @State var adminChannelEnabled = false + + var body: some View { + VStack { + Form { + ConfigHeader(title: "Security", config: \.securityConfig, node: node, onAppear: setSecurityValues) + + Section(header: Text("Admin & Direct Message Keys")) { + VStack(alignment: .leading) { + Label("Public Key", systemImage: "key") + SecureInput("Public Key", text: $publicKey, isValid: $hasValidPublicKey) + .background( + RoundedRectangle(cornerRadius: 10.0) + .stroke(hasValidPublicKey ? Color.clear : Color.red, lineWidth: 2.0) + ) + Text("Sent out to other nodes on the mesh to allow them to compute a shared secret key.") + .foregroundStyle(.secondary) + .font(idiom == .phone ? .caption : .callout) + } + VStack(alignment: .leading) { + Label("Private Key", systemImage: "key.fill") + SecureInput("Private Key", text: $privateKey, isValid: $hasValidPrivateKey) + .background( + RoundedRectangle(cornerRadius: 10.0) + .stroke(hasValidPrivateKey ? Color.clear : Color.red, lineWidth: 2.0) + ) + Text("Used to create a shared key with a remote device.") + .foregroundStyle(.secondary) + .font(idiom == .phone ? .caption : .callout) + } + VStack(alignment: .leading) { + Label("Admin Key", systemImage: "key.viewfinder") + SecureInput("Admin Key", text: $adminKey, isValid: $hasValidAdminKey) + Text("The public key authorized to send admin messages to this node.") + .foregroundStyle(.secondary) + .font(idiom == .phone ? .caption : .callout) + } + } + Section(header: Text("Logs")) { + Toggle(isOn: $bluetoothLoggingEnabled) { + Label("Bluetooth Logs", systemImage: "dot.radiowaves.right") + Text("View and export position-redacted device logs over Bluetooth") + Link("View Logs", destination: URL(string: "meshtastic:///settings/debugLogs")!) + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + Section(header: Text("Administration")) { + if adminKey.length > 0 || adminChannelEnabled { + Toggle(isOn: $isManaged) { + Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath") + Text("Device is managed by a mesh administrator.") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + Toggle(isOn: $adminChannelEnabled) { + Label("Legacy Administration", systemImage: "lock.slash") + Text("Allow incoming device control over the insecure legacy admin channel.") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + Section(header: Text("Developer")) { + Toggle(isOn: $serialEnabled) { + Label("Serial Console", systemImage: "terminal") + Text("Serial Console over the Stream API.") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + if serialEnabled { + Toggle(isOn: $debugLogApiEnabled) { + Label("Serial Debug Logs", systemImage: "ant.fill") + Text("Output live debug logging over serial.") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + } + } + } + .scrollDismissesKeyboard(.immediately) + .navigationTitle("Security Config") + .navigationBarItems(trailing: ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: "\(bleManager.connectedPeripheral?.shortName ?? "?")" + ) + }) + .onChange(of: isManaged) { + if $0 != node?.securityConfig?.isManaged { hasChanges = true } + } + .onChange(of: serialEnabled) { + if $0 != node?.securityConfig?.serialEnabled { hasChanges = true } + } + .onChange(of: debugLogApiEnabled) { + if $0 != node?.securityConfig?.debugLogApiEnabled { hasChanges = true } + } + .onChange(of: bluetoothLoggingEnabled) { + if $0 != node?.securityConfig?.bluetoothLoggingEnabled { hasChanges = true } + } + .onChange(of: adminChannelEnabled) { + if $0 != node?.securityConfig?.adminChannelEnabled { hasChanges = true } + } + .onChange(of: publicKey) { _ in + let tempKey = Data(base64Encoded: publicKey) ?? Data() + if tempKey.count == 32 { + hasValidPublicKey = true + } else { + hasValidPublicKey = false + } + hasChanges = true + } + .onChange(of: privateKey) { _ in + let tempKey = Data(base64Encoded: privateKey) ?? Data() + if tempKey.count == 32 { + hasValidPrivateKey = true + } else { + hasValidPrivateKey = false + } + hasChanges = true + } + .onChange(of: adminKey) { key in + let tempKey = Data(base64Encoded: key) ?? Data() + if key.isEmpty { + hasValidAdminKey = true + } else if tempKey.count == 32 { + hasValidAdminKey = true + } else { + hasValidAdminKey = false + } + hasChanges = true + } + .onFirstAppear { + // Need to request a Power config from the remote node before allowing changes + if bleManager.connectedPeripheral != nil && node?.securityConfig == nil { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) + if node != nil && connectedNode != nil { + _ = bleManager.requestSecurityConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + } + } + } + + SaveConfigButton(node: node, hasChanges: $hasChanges) { + + if !hasValidPublicKey || !hasValidPrivateKey || !hasValidAdminKey { + return + } + + guard let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context), + let fromUser = connectedNode.user, + let toUser = node?.user else { + return + } + + var config = Config.SecurityConfig() + config.publicKey = Data(base64Encoded: publicKey) ?? Data() + config.privateKey = Data(base64Encoded: privateKey) ?? Data() + config.adminKey = Data(base64Encoded: adminKey) ?? Data() + config.isManaged = isManaged + config.serialEnabled = serialEnabled + config.debugLogApiEnabled = debugLogApiEnabled + config.bluetoothLoggingEnabled = bluetoothLoggingEnabled + config.adminChannelEnabled = adminChannelEnabled + + let adminMessageId = bleManager.saveSecurityConfig( + config: config, + fromUser: fromUser, + toUser: toUser, + adminIndex: connectedNode.myInfo?.adminIndex ?? 0 + ) + if adminMessageId > 0 { + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + goBack() + } + } + } + + func setSecurityValues() { + self.publicKey = node?.securityConfig?.publicKey?.base64EncodedString() ?? "" + self.privateKey = node?.securityConfig?.privateKey?.base64EncodedString() ?? "" + self.adminKey = node?.securityConfig?.adminKey?.base64EncodedString() ?? "" + self.isManaged = node?.securityConfig?.isManaged ?? false + self.serialEnabled = node?.securityConfig?.serialEnabled ?? false + self.debugLogApiEnabled = node?.securityConfig?.debugLogApiEnabled ?? false + self.bluetoothLoggingEnabled = node?.securityConfig?.bluetoothLoggingEnabled ?? false + self.adminChannelEnabled = node?.securityConfig?.adminChannelEnabled ?? false + self.hasChanges = false + } +} diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index e591761f..9a450775 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -13,7 +13,7 @@ struct Firmware: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager var node: NodeInfoEntity? - @State var minimumVersion = "2.4.0" + @State var minimumVersion = "2.4.2" @State var version = "" @State private var currentDevice: DeviceHardware? @State private var latestStable: FirmwareRelease? diff --git a/Meshtastic/Views/Settings/Routes.swift b/Meshtastic/Views/Settings/Routes.swift index cc70de3b..65be4fd3 100644 --- a/Meshtastic/Views/Settings/Routes.swift +++ b/Meshtastic/Views/Settings/Routes.swift @@ -58,6 +58,7 @@ struct Routes: View { } do { + guard let fileContent = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return } let routeName = selectedFile.lastPathComponent.dropLast(4) let lines = fileContent.components(separatedBy: "\n") diff --git a/Meshtastic/Views/Settings/SaveChannelQRCode.swift b/Meshtastic/Views/Settings/SaveChannelQRCode.swift index fee7877b..ac8138fa 100644 --- a/Meshtastic/Views/Settings/SaveChannelQRCode.swift +++ b/Meshtastic/Views/Settings/SaveChannelQRCode.swift @@ -50,6 +50,18 @@ struct SaveChannelQRCode: View { .controlSize(.large) .padding() .disabled(!connectedToDevice) +#if targetEnvironment(macCatalyst) + Button { + dismiss() + } label: { + Label("cancel", systemImage: "xmark") + + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() +#endif } else { Button { dismiss() @@ -62,19 +74,6 @@ struct SaveChannelQRCode: View { .controlSize(.large) .padding() } - - #if targetEnvironment(macCatalyst) - Button { - dismiss() - } label: { - Label("cancel", systemImage: "xmark") - - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - #endif } } .onAppear { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 880dabf2..814f3ab9 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -17,6 +17,8 @@ struct Settings: View { @FetchRequest( sortDescriptors: [ NSSortDescriptor(key: "favorite", ascending: false), + NSSortDescriptor(key: "user.pkiEncrypted", ascending: false), + NSSortDescriptor(key: "viaMqtt", ascending: true), NSSortDescriptor(key: "user.longName", ascending: true) ], animation: .default @@ -73,6 +75,14 @@ struct Settings: View { } .disabled(selectedNode > 0 && selectedNode != preferredNodeNum) + NavigationLink(value: SettingsNavigationState.security) { + Label { + Text("Security") + } icon: { + Image(systemName: "lock.shield") + } + } + NavigationLink(value: SettingsNavigationState.shareQRCode) { Label { Text("share.channels") @@ -335,18 +345,16 @@ struct Settings: View { } } - let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 - if !(node?.deviceConfig?.isManaged ?? false) { if bleManager.connectedPeripheral != nil { Section("Configure") { - if hasAdmin { + if node?.canRemoteAdmin ?? false { Picker("Configuring Node", selection: $selectedNode) { if selectedNode == 0 { Text("Connect to a Node").tag(0) } - ForEach(nodes) { node in + /// Connected Node if node.num == bleManager.connectedPeripheral?.num ?? 0 { Label { Text("BLE: \(node.user?.longName ?? "unknown".localized)") @@ -354,16 +362,30 @@ struct Settings: View { Image(systemName: "antenna.radiowaves.left.and.right") } .tag(Int(node.num)) - } else if node.metadata != nil { + } else if node.canRemoteAdmin && UserDefaults.enableAdministration && node.sessionPasskey != nil { /// Nodes using the new PKI system Label { - Text("Remote: \(node.user?.longName ?? "unknown".localized)") + Text("Remote PKI Admin: \(node.user?.longName ?? "unknown".localized)") } icon: { Image(systemName: "av.remote") } .tag(Int(node.num)) - } else if hasAdmin { + } else if !UserDefaults.enableAdministration && node.metadata != nil { /// Nodes using the old admin system Label { - Text("Request Admin: \(node.user?.longName ?? "unknown".localized)") + Text("Remote Legacy Admin: \(node.user?.longName ?? "unknown".localized)") + } icon: { + Image(systemName: "av.remote") + } + .tag(Int(node.num)) + } else if UserDefaults.enableAdministration && node.user?.pkiEncrypted ?? false { + Label { + Text("Request PKI Admin: \(node.user?.longName ?? "unknown".localized)") + } icon: { + Image(systemName: "rectangle.and.hand.point.up.left") + } + .tag(Int(node.num)) + } else if !UserDefaults.enableAdministration { + Label { + Text("Request Legacy Admin: \(node.user?.longName ?? "unknown".localized)") } icon: { Image(systemName: "rectangle.and.hand.point.up.left") } @@ -378,7 +400,7 @@ struct Settings: View { let node = nodes.first(where: { $0.num == newValue }) let connectedNode = nodes.first(where: { $0.num == preferredNodeNum }) preferredNodeNum = Int(connectedNode?.num ?? 0)// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil { + if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil {// && node?.metadata == nil { let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) if adminMessageId > 0 { Logger.mesh.info("Sent node metadata request from node details") @@ -461,6 +483,8 @@ struct Settings: View { PaxCounterConfig(node: nodes.first(where: { $0.num == selectedNode })) case .ringtone: RtttlConfig(node: nodes.first(where: { $0.num == selectedNode })) + case .security: + SecurityConfig(node: nodes.first(where: { $0.num == selectedNode })) case .serial: SerialConfig(node: nodes.first(where: { $0.num == selectedNode })) case .storeAndForward: diff --git a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift index 37528079..0f1c3586 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift @@ -24,11 +24,17 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// This message is handled by the Admin module and is responsible for all settings/channel read/write operations. /// This message is used to do settings operations to both remote AND local nodes. /// (Prior to 1.2 these operations were done via special ToRadio operations) -public struct AdminMessage: Sendable { +public struct AdminMessage { // 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. + /// + /// The node generates this key and sends it with any get_x_response packets. + /// The client MUST include the same key with any set_x commands. Key expires after 300 seconds. + /// Prevents replay attacks for admin messages. + public var sessionPasskey: Data = Data() + /// /// TODO: REPLACE public var payloadVariant: AdminMessage.OneOf_PayloadVariant? = nil @@ -369,6 +375,17 @@ public struct AdminMessage: Sendable { set {payloadVariant = .removeFixedPosition(newValue)} } + /// + /// Set time only on the node + /// Convenience method to set the time on the node (as Net quality) without any other position data + public var setTimeOnly: UInt32 { + get { + if case .setTimeOnly(let v)? = payloadVariant {return v} + return 0 + } + set {payloadVariant = .setTimeOnly(newValue)} + } + /// /// Begins an edit transaction for config, module config, owner, and channel settings changes /// This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) @@ -390,6 +407,16 @@ public struct AdminMessage: Sendable { set {payloadVariant = .commitEditSettings(newValue)} } + /// + /// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. + public var factoryResetDevice: Int32 { + get { + if case .factoryResetDevice(let v)? = payloadVariant {return v} + return 0 + } + set {payloadVariant = .factoryResetDevice(newValue)} + } + /// /// Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) /// Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. @@ -433,13 +460,13 @@ public struct AdminMessage: Sendable { } /// - /// Tell the node to factory reset, all device settings will be returned to factory defaults. - public var factoryReset: Int32 { + /// Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. + public var factoryResetConfig: Int32 { get { - if case .factoryReset(let v)? = payloadVariant {return v} + if case .factoryResetConfig(let v)? = payloadVariant {return v} return 0 } - set {payloadVariant = .factoryReset(newValue)} + set {payloadVariant = .factoryResetConfig(newValue)} } /// @@ -456,7 +483,7 @@ public struct AdminMessage: Sendable { /// /// TODO: REPLACE - public enum OneOf_PayloadVariant: Equatable, Sendable { + public enum OneOf_PayloadVariant: Equatable { /// /// Send the specified channel in the response to this message /// NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present) @@ -563,6 +590,10 @@ public struct AdminMessage: Sendable { /// Clear fixed position coordinates and then set position.fixed_position = false case removeFixedPosition(Bool) /// + /// Set time only on the node + /// Convenience method to set the time on the node (as Net quality) without any other position data + case setTimeOnly(UInt32) + /// /// Begins an edit transaction for config, module config, owner, and channel settings changes /// This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) case beginEditSettings(Bool) @@ -570,6 +601,9 @@ public struct AdminMessage: Sendable { /// Commits an open transaction for any edits made to config, module config, owner, and channel settings case commitEditSettings(Bool) /// + /// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. + case factoryResetDevice(Int32) + /// /// Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot) /// Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. case rebootOtaSeconds(Int32) @@ -584,17 +618,199 @@ public struct AdminMessage: Sendable { /// Tell the node to shutdown in this many seconds (or <0 to cancel shutdown) case shutdownSeconds(Int32) /// - /// Tell the node to factory reset, all device settings will be returned to factory defaults. - case factoryReset(Int32) + /// Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved. + case factoryResetConfig(Int32) /// /// Tell the node to reset the nodedb. case nodedbReset(Int32) + #if !swift(>=4.1) + public static func ==(lhs: AdminMessage.OneOf_PayloadVariant, rhs: AdminMessage.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.getChannelRequest, .getChannelRequest): return { + guard case .getChannelRequest(let l) = lhs, case .getChannelRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getChannelResponse, .getChannelResponse): return { + guard case .getChannelResponse(let l) = lhs, case .getChannelResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getOwnerRequest, .getOwnerRequest): return { + guard case .getOwnerRequest(let l) = lhs, case .getOwnerRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getOwnerResponse, .getOwnerResponse): return { + guard case .getOwnerResponse(let l) = lhs, case .getOwnerResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getConfigRequest, .getConfigRequest): return { + guard case .getConfigRequest(let l) = lhs, case .getConfigRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getConfigResponse, .getConfigResponse): return { + guard case .getConfigResponse(let l) = lhs, case .getConfigResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getModuleConfigRequest, .getModuleConfigRequest): return { + guard case .getModuleConfigRequest(let l) = lhs, case .getModuleConfigRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getModuleConfigResponse, .getModuleConfigResponse): return { + guard case .getModuleConfigResponse(let l) = lhs, case .getModuleConfigResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getCannedMessageModuleMessagesRequest, .getCannedMessageModuleMessagesRequest): return { + guard case .getCannedMessageModuleMessagesRequest(let l) = lhs, case .getCannedMessageModuleMessagesRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getCannedMessageModuleMessagesResponse, .getCannedMessageModuleMessagesResponse): return { + guard case .getCannedMessageModuleMessagesResponse(let l) = lhs, case .getCannedMessageModuleMessagesResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getDeviceMetadataRequest, .getDeviceMetadataRequest): return { + guard case .getDeviceMetadataRequest(let l) = lhs, case .getDeviceMetadataRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getDeviceMetadataResponse, .getDeviceMetadataResponse): return { + guard case .getDeviceMetadataResponse(let l) = lhs, case .getDeviceMetadataResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getRingtoneRequest, .getRingtoneRequest): return { + guard case .getRingtoneRequest(let l) = lhs, case .getRingtoneRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getRingtoneResponse, .getRingtoneResponse): return { + guard case .getRingtoneResponse(let l) = lhs, case .getRingtoneResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getDeviceConnectionStatusRequest, .getDeviceConnectionStatusRequest): return { + guard case .getDeviceConnectionStatusRequest(let l) = lhs, case .getDeviceConnectionStatusRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getDeviceConnectionStatusResponse, .getDeviceConnectionStatusResponse): return { + guard case .getDeviceConnectionStatusResponse(let l) = lhs, case .getDeviceConnectionStatusResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setHamMode, .setHamMode): return { + guard case .setHamMode(let l) = lhs, case .setHamMode(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getNodeRemoteHardwarePinsRequest, .getNodeRemoteHardwarePinsRequest): return { + guard case .getNodeRemoteHardwarePinsRequest(let l) = lhs, case .getNodeRemoteHardwarePinsRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getNodeRemoteHardwarePinsResponse, .getNodeRemoteHardwarePinsResponse): return { + guard case .getNodeRemoteHardwarePinsResponse(let l) = lhs, case .getNodeRemoteHardwarePinsResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.enterDfuModeRequest, .enterDfuModeRequest): return { + guard case .enterDfuModeRequest(let l) = lhs, case .enterDfuModeRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.deleteFileRequest, .deleteFileRequest): return { + guard case .deleteFileRequest(let l) = lhs, case .deleteFileRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setScale, .setScale): return { + guard case .setScale(let l) = lhs, case .setScale(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setOwner, .setOwner): return { + guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setChannel, .setChannel): return { + guard case .setChannel(let l) = lhs, case .setChannel(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setConfig, .setConfig): return { + guard case .setConfig(let l) = lhs, case .setConfig(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setModuleConfig, .setModuleConfig): return { + guard case .setModuleConfig(let l) = lhs, case .setModuleConfig(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setCannedMessageModuleMessages, .setCannedMessageModuleMessages): return { + guard case .setCannedMessageModuleMessages(let l) = lhs, case .setCannedMessageModuleMessages(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setRingtoneMessage, .setRingtoneMessage): return { + guard case .setRingtoneMessage(let l) = lhs, case .setRingtoneMessage(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.removeByNodenum, .removeByNodenum): return { + guard case .removeByNodenum(let l) = lhs, case .removeByNodenum(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setFavoriteNode, .setFavoriteNode): return { + guard case .setFavoriteNode(let l) = lhs, case .setFavoriteNode(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.removeFavoriteNode, .removeFavoriteNode): return { + guard case .removeFavoriteNode(let l) = lhs, case .removeFavoriteNode(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setFixedPosition, .setFixedPosition): return { + guard case .setFixedPosition(let l) = lhs, case .setFixedPosition(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.removeFixedPosition, .removeFixedPosition): return { + guard case .removeFixedPosition(let l) = lhs, case .removeFixedPosition(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.setTimeOnly, .setTimeOnly): return { + guard case .setTimeOnly(let l) = lhs, case .setTimeOnly(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.beginEditSettings, .beginEditSettings): return { + guard case .beginEditSettings(let l) = lhs, case .beginEditSettings(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.commitEditSettings, .commitEditSettings): return { + guard case .commitEditSettings(let l) = lhs, case .commitEditSettings(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.factoryResetDevice, .factoryResetDevice): return { + guard case .factoryResetDevice(let l) = lhs, case .factoryResetDevice(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.rebootOtaSeconds, .rebootOtaSeconds): return { + guard case .rebootOtaSeconds(let l) = lhs, case .rebootOtaSeconds(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.exitSimulator, .exitSimulator): return { + guard case .exitSimulator(let l) = lhs, case .exitSimulator(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.rebootSeconds, .rebootSeconds): return { + guard case .rebootSeconds(let l) = lhs, case .rebootSeconds(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.shutdownSeconds, .shutdownSeconds): return { + guard case .shutdownSeconds(let l) = lhs, case .shutdownSeconds(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.factoryResetConfig, .factoryResetConfig): return { + guard case .factoryResetConfig(let l) = lhs, case .factoryResetConfig(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.nodedbReset, .nodedbReset): return { + guard case .nodedbReset(let l) = lhs, case .nodedbReset(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } /// /// TODO: REPLACE - public enum ConfigType: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum ConfigType: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -624,6 +840,10 @@ public struct AdminMessage: Sendable { /// /// TODO: REPLACE case bluetoothConfig // = 6 + + /// + /// TODO: REPLACE + case securityConfig // = 7 case UNRECOGNIZED(Int) public init() { @@ -639,6 +859,7 @@ public struct AdminMessage: Sendable { case 4: self = .displayConfig case 5: self = .loraConfig case 6: self = .bluetoothConfig + case 7: self = .securityConfig default: self = .UNRECOGNIZED(rawValue) } } @@ -652,26 +873,16 @@ public struct AdminMessage: Sendable { case .displayConfig: return 4 case .loraConfig: return 5 case .bluetoothConfig: return 6 + case .securityConfig: return 7 case .UNRECOGNIZED(let i): return i } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [AdminMessage.ConfigType] = [ - .deviceConfig, - .positionConfig, - .powerConfig, - .networkConfig, - .displayConfig, - .loraConfig, - .bluetoothConfig, - ] - } /// /// TODO: REPLACE - public enum ModuleConfigType: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum ModuleConfigType: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -769,31 +980,51 @@ public struct AdminMessage: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [AdminMessage.ModuleConfigType] = [ - .mqttConfig, - .serialConfig, - .extnotifConfig, - .storeforwardConfig, - .rangetestConfig, - .telemetryConfig, - .cannedmsgConfig, - .audioConfig, - .remotehardwareConfig, - .neighborinfoConfig, - .ambientlightingConfig, - .detectionsensorConfig, - .paxcounterConfig, - ] - } public init() {} } +#if swift(>=4.2) + +extension AdminMessage.ConfigType: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [AdminMessage.ConfigType] = [ + .deviceConfig, + .positionConfig, + .powerConfig, + .networkConfig, + .displayConfig, + .loraConfig, + .bluetoothConfig, + .securityConfig, + ] +} + +extension AdminMessage.ModuleConfigType: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [AdminMessage.ModuleConfigType] = [ + .mqttConfig, + .serialConfig, + .extnotifConfig, + .storeforwardConfig, + .rangetestConfig, + .telemetryConfig, + .cannedmsgConfig, + .audioConfig, + .remotehardwareConfig, + .neighborinfoConfig, + .ambientlightingConfig, + .detectionsensorConfig, + .paxcounterConfig, + ] +} + +#endif // swift(>=4.2) + /// /// Parameters for setting up Meshtastic for ameteur radio usage -public struct HamParameters: Sendable { +public struct HamParameters { // 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. @@ -823,7 +1054,7 @@ public struct HamParameters: Sendable { /// /// Response envelope for node_remote_hardware_pins -public struct NodeRemoteHardwarePinsResponse: Sendable { +public struct NodeRemoteHardwarePinsResponse { // 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. @@ -837,6 +1068,15 @@ public struct NodeRemoteHardwarePinsResponse: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension AdminMessage: @unchecked Sendable {} +extension AdminMessage.OneOf_PayloadVariant: @unchecked Sendable {} +extension AdminMessage.ConfigType: @unchecked Sendable {} +extension AdminMessage.ModuleConfigType: @unchecked Sendable {} +extension HamParameters: @unchecked Sendable {} +extension NodeRemoteHardwarePinsResponse: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -844,6 +1084,7 @@ fileprivate let _protobuf_package = "meshtastic" extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".AdminMessage" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 101: .standard(proto: "session_passkey"), 1: .standard(proto: "get_channel_request"), 2: .standard(proto: "get_channel_response"), 3: .standard(proto: "get_owner_request"), @@ -877,13 +1118,15 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 40: .standard(proto: "remove_favorite_node"), 41: .standard(proto: "set_fixed_position"), 42: .standard(proto: "remove_fixed_position"), + 43: .standard(proto: "set_time_only"), 64: .standard(proto: "begin_edit_settings"), 65: .standard(proto: "commit_edit_settings"), + 94: .standard(proto: "factory_reset_device"), 95: .standard(proto: "reboot_ota_seconds"), 96: .standard(proto: "exit_simulator"), 97: .standard(proto: "reboot_seconds"), 98: .standard(proto: "shutdown_seconds"), - 99: .standard(proto: "factory_reset"), + 99: .standard(proto: "factory_reset_config"), 100: .standard(proto: "nodedb_reset"), ] @@ -1222,6 +1465,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .removeFixedPosition(v) } }() + case 43: try { + var v: UInt32? + try decoder.decodeSingularFixed32Field(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .setTimeOnly(v) + } + }() case 64: try { var v: Bool? try decoder.decodeSingularBoolField(value: &v) @@ -1238,6 +1489,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .commitEditSettings(v) } }() + case 94: try { + var v: Int32? + try decoder.decodeSingularInt32Field(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .factoryResetDevice(v) + } + }() case 95: try { var v: Int32? try decoder.decodeSingularInt32Field(value: &v) @@ -1275,7 +1534,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat try decoder.decodeSingularInt32Field(value: &v) if let v = v { if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} - self.payloadVariant = .factoryReset(v) + self.payloadVariant = .factoryResetConfig(v) } }() case 100: try { @@ -1286,6 +1545,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .nodedbReset(v) } }() + case 101: try { try decoder.decodeSingularBytesField(value: &self.sessionPasskey) }() default: break } } @@ -1429,6 +1689,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .removeFixedPosition(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularBoolField(value: v, fieldNumber: 42) }() + case .setTimeOnly?: try { + guard case .setTimeOnly(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularFixed32Field(value: v, fieldNumber: 43) + }() case .beginEditSettings?: try { guard case .beginEditSettings(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularBoolField(value: v, fieldNumber: 64) @@ -1437,6 +1701,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .commitEditSettings(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularBoolField(value: v, fieldNumber: 65) }() + case .factoryResetDevice?: try { + guard case .factoryResetDevice(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularInt32Field(value: v, fieldNumber: 94) + }() case .rebootOtaSeconds?: try { guard case .rebootOtaSeconds(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularInt32Field(value: v, fieldNumber: 95) @@ -1453,8 +1721,8 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .shutdownSeconds(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularInt32Field(value: v, fieldNumber: 98) }() - case .factoryReset?: try { - guard case .factoryReset(let v)? = self.payloadVariant else { preconditionFailure() } + case .factoryResetConfig?: try { + guard case .factoryResetConfig(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularInt32Field(value: v, fieldNumber: 99) }() case .nodedbReset?: try { @@ -1463,10 +1731,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat }() case nil: break } + if !self.sessionPasskey.isEmpty { + try visitor.visitSingularBytesField(value: self.sessionPasskey, fieldNumber: 101) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: AdminMessage, rhs: AdminMessage) -> Bool { + if lhs.sessionPasskey != rhs.sessionPasskey {return false} if lhs.payloadVariant != rhs.payloadVariant {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true @@ -1482,6 +1754,7 @@ extension AdminMessage.ConfigType: SwiftProtobuf._ProtoNameProviding { 4: .same(proto: "DISPLAY_CONFIG"), 5: .same(proto: "LORA_CONFIG"), 6: .same(proto: "BLUETOOTH_CONFIG"), + 7: .same(proto: "SECURITY_CONFIG"), ] } @@ -1534,7 +1807,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if self.txPower != 0 { try visitor.visitSingularInt32Field(value: self.txPower, fieldNumber: 2) } - if self.frequency.bitPattern != 0 { + if self.frequency != 0 { try visitor.visitSingularFloatField(value: self.frequency, fieldNumber: 3) } if !self.shortName.isEmpty { diff --git a/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift index 18e66d8e..0457077c 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/apponly.pb.swift @@ -26,7 +26,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// any SECONDARY channels. /// No DISABLED channels are included. /// This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL -public struct ChannelSet: Sendable { +public struct ChannelSet { // 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. @@ -53,6 +53,10 @@ public struct ChannelSet: Sendable { fileprivate var _loraConfig: Config.LoRaConfig? = nil } +#if swift(>=5.5) && canImport(_Concurrency) +extension ChannelSet: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift index 1dd12469..4406deb3 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/atak.pb.swift @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public enum Team: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum Team: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -130,6 +130,11 @@ public enum Team: SwiftProtobuf.Enum, Swift.CaseIterable { } } +} + +#if swift(>=4.2) + +extension Team: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [Team] = [ .unspecifedColor, @@ -148,12 +153,13 @@ public enum Team: SwiftProtobuf.Enum, Swift.CaseIterable { .darkGreen, .brown, ] - } +#endif // swift(>=4.2) + /// /// Role of the group member -public enum MemberRole: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum MemberRole: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -227,6 +233,11 @@ public enum MemberRole: SwiftProtobuf.Enum, Swift.CaseIterable { } } +} + +#if swift(>=4.2) + +extension MemberRole: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [MemberRole] = [ .unspecifed, @@ -239,12 +250,13 @@ public enum MemberRole: SwiftProtobuf.Enum, Swift.CaseIterable { .rto, .k9, ] - } +#endif // swift(>=4.2) + /// /// Packets for the official ATAK Plugin -public struct TAKPacket: Sendable { +public struct TAKPacket { // 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. @@ -314,7 +326,7 @@ public struct TAKPacket: Sendable { /// /// The payload of the packet - public enum OneOf_PayloadVariant: Equatable, Sendable { + public enum OneOf_PayloadVariant: Equatable { /// /// TAK position report case pli(PLI) @@ -322,6 +334,24 @@ public struct TAKPacket: Sendable { /// ATAK GeoChat message case chat(GeoChat) + #if !swift(>=4.1) + public static func ==(lhs: TAKPacket.OneOf_PayloadVariant, rhs: TAKPacket.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.pli, .pli): return { + guard case .pli(let l) = lhs, case .pli(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.chat, .chat): return { + guard case .chat(let l) = lhs, case .chat(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } public init() {} @@ -333,7 +363,7 @@ public struct TAKPacket: Sendable { /// /// ATAK GeoChat message -public struct GeoChat: Sendable { +public struct GeoChat { // 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. @@ -375,7 +405,7 @@ public struct GeoChat: Sendable { /// /// ATAK Group /// <__group role='Team Member' name='Cyan'/> -public struct Group: Sendable { +public struct Group { // 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. @@ -397,7 +427,7 @@ public struct Group: Sendable { /// /// ATAK EUD Status /// -public struct Status: Sendable { +public struct Status { // 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. @@ -414,7 +444,7 @@ public struct Status: Sendable { /// /// ATAK Contact /// -public struct Contact: Sendable { +public struct Contact { // 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. @@ -434,7 +464,7 @@ public struct Contact: Sendable { /// /// Position Location Information from ATAK -public struct PLI: Sendable { +public struct PLI { // 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. @@ -466,6 +496,18 @@ public struct PLI: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension Team: @unchecked Sendable {} +extension MemberRole: @unchecked Sendable {} +extension TAKPacket: @unchecked Sendable {} +extension TAKPacket.OneOf_PayloadVariant: @unchecked Sendable {} +extension GeoChat: @unchecked Sendable {} +extension Group: @unchecked Sendable {} +extension Status: @unchecked Sendable {} +extension Contact: @unchecked Sendable {} +extension PLI: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift index a43393e1..1b8c84de 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/cannedmessages.pb.swift @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// Canned message module configuration. -public struct CannedMessageModuleConfig: Sendable { +public struct CannedMessageModuleConfig { // 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. @@ -36,6 +36,10 @@ public struct CannedMessageModuleConfig: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension CannedMessageModuleConfig: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift index a8c96595..5b9c7e49 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/channel.pb.swift @@ -36,15 +36,13 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// FIXME: Add description of multi-channel support and how primary vs secondary channels are used. /// FIXME: explain how apps use channels for security. /// explain how remote settings and remote gpio are managed as an example -public struct ChannelSettings: @unchecked Sendable { +public struct ChannelSettings { // 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. /// /// Deprecated in favor of LoraConfig.channel_num - /// - /// NOTE: This field was marked as deprecated in the .proto file. public var channelNum: UInt32 = 0 /// @@ -113,7 +111,7 @@ public struct ChannelSettings: @unchecked Sendable { /// /// This message is specifically for modules to store per-channel configuration data. -public struct ModuleSettings: Sendable { +public struct ModuleSettings { // 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. @@ -134,7 +132,7 @@ public struct ModuleSettings: Sendable { /// /// A pair of a channel number, mode and the (sharable) settings for that channel -public struct Channel: Sendable { +public struct Channel { // 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. @@ -172,7 +170,7 @@ public struct Channel: Sendable { /// cross band routing as needed. /// If a device has only a single radio (the common case) only one channel can be PRIMARY at a time /// (but any number of SECONDARY channels can't be sent received on that common frequency) - public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Role: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -211,13 +209,6 @@ public struct Channel: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Channel.Role] = [ - .disabled, - .primary, - .secondary, - ] - } public init() {} @@ -225,6 +216,26 @@ public struct Channel: Sendable { fileprivate var _settings: ChannelSettings? = nil } +#if swift(>=4.2) + +extension Channel.Role: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Channel.Role] = [ + .disabled, + .primary, + .secondary, + ] +} + +#endif // swift(>=4.2) + +#if swift(>=5.5) && canImport(_Concurrency) +extension ChannelSettings: @unchecked Sendable {} +extension ModuleSettings: @unchecked Sendable {} +extension Channel: @unchecked Sendable {} +extension Channel.Role: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift index 89370cc5..c3d93bf7 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/clientonly.pb.swift @@ -23,7 +23,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// This abstraction is used to contain any configuration for provisioning a node on any client. /// It is useful for importing and exporting configurations. -public struct DeviceProfile: Sendable { +public struct DeviceProfile { // 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. @@ -94,6 +94,10 @@ public struct DeviceProfile: Sendable { fileprivate var _moduleConfig: LocalModuleConfig? = nil } +#if swift(>=5.5) && canImport(_Concurrency) +extension DeviceProfile: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift index aeaf9054..4b953470 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct Config: Sendable { +public struct Config { // 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. @@ -85,11 +85,19 @@ public struct Config: Sendable { set {payloadVariant = .bluetooth(newValue)} } + public var security: Config.SecurityConfig { + get { + if case .security(let v)? = payloadVariant {return v} + return Config.SecurityConfig() + } + set {payloadVariant = .security(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() /// /// Payload Variant - public enum OneOf_PayloadVariant: Equatable, Sendable { + public enum OneOf_PayloadVariant: Equatable { case device(Config.DeviceConfig) case position(Config.PositionConfig) case power(Config.PowerConfig) @@ -97,12 +105,55 @@ public struct Config: Sendable { case display(Config.DisplayConfig) case lora(Config.LoRaConfig) case bluetooth(Config.BluetoothConfig) + case security(Config.SecurityConfig) + #if !swift(>=4.1) + public static func ==(lhs: Config.OneOf_PayloadVariant, rhs: Config.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.device, .device): return { + guard case .device(let l) = lhs, case .device(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.position, .position): return { + guard case .position(let l) = lhs, case .position(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.power, .power): return { + guard case .power(let l) = lhs, case .power(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.network, .network): return { + guard case .network(let l) = lhs, case .network(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.display, .display): return { + guard case .display(let l) = lhs, case .display(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.lora, .lora): return { + guard case .lora(let l) = lhs, case .lora(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.bluetooth, .bluetooth): return { + guard case .bluetooth(let l) = lhs, case .bluetooth(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.security, .security): return { + guard case .security(let l) = lhs, case .security(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } /// /// Configuration - public struct DeviceConfig: Sendable { + public struct DeviceConfig { // 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. @@ -113,11 +164,13 @@ public struct Config: Sendable { /// /// Disabling this will disable the SerialConsole by not initilizing the StreamAPI + /// Moved to SecurityConfig public var serialEnabled: Bool = false /// /// By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). /// Set this to true to leave the debug log outputting even when API is active. + /// Moved to SecurityConfig public var debugLogEnabled: Bool = false /// @@ -146,6 +199,7 @@ public struct Config: Sendable { /// /// If true, device is considered to be "managed" by a mesh administrator /// Clients should then limit available configuration and administrative options inside the user interface + /// Moved to SecurityConfig public var isManaged: Bool = false /// @@ -164,7 +218,7 @@ public struct Config: Sendable { /// /// Defines the device's role on the Mesh network - public enum Role: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Role: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -182,8 +236,6 @@ public struct Config: Sendable { /// The wifi radio and the oled screen will be put to sleep. /// This mode may still potentially have higher power usage due to it's preference in message rebroadcasting on the mesh. case router // = 2 - - /// NOTE: This enum value was marked as deprecated in the .proto file case routerClient // = 3 /// @@ -274,26 +326,11 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DeviceConfig.Role] = [ - .client, - .clientMute, - .router, - .routerClient, - .repeater, - .tracker, - .sensor, - .tak, - .clientHidden, - .lostAndFound, - .takTracker, - ] - } /// /// Defines the device's behavior for how messages are rebroadcast - public enum RebroadcastMode: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum RebroadcastMode: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -341,14 +378,6 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DeviceConfig.RebroadcastMode] = [ - .all, - .allSkipDecoding, - .localOnly, - .knownOnly, - ] - } public init() {} @@ -356,7 +385,7 @@ public struct Config: Sendable { /// /// Position Config - public struct PositionConfig: Sendable { + public struct PositionConfig { // 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. @@ -378,8 +407,6 @@ public struct Config: Sendable { /// /// Is GPS enabled for this node? - /// - /// NOTE: This field was marked as deprecated in the .proto file. public var gpsEnabled: Bool = false /// @@ -390,8 +417,6 @@ public struct Config: Sendable { /// /// Deprecated in favor of using smart / regular broadcast intervals as implicit attempt time - /// - /// NOTE: This field was marked as deprecated in the .proto file. public var gpsAttemptTime: UInt32 = 0 /// @@ -432,7 +457,7 @@ public struct Config: Sendable { /// are always included (also time if GPS-synced) /// NOTE: the more fields are included, the larger the message will be - /// leading to longer airtime and a higher risk of packet loss - public enum PositionFlags: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum PositionFlags: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -522,24 +547,9 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.PositionConfig.PositionFlags] = [ - .unset, - .altitude, - .altitudeMsl, - .geoidalSeparation, - .dop, - .hvdop, - .satinview, - .seqNo, - .timestamp, - .heading, - .speed, - ] - } - public enum GpsMode: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum GpsMode: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -577,13 +587,6 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.PositionConfig.GpsMode] = [ - .disabled, - .enabled, - .notPresent, - ] - } public init() {} @@ -592,7 +595,7 @@ public struct Config: Sendable { /// /// Power Config\ /// See [Power Config](/docs/settings/config/power) for additional power config details. - public struct PowerConfig: Sendable { + public struct PowerConfig { // 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. @@ -652,7 +655,7 @@ public struct Config: Sendable { /// /// Network Config - public struct NetworkConfig: Sendable { + public struct NetworkConfig { // 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. @@ -699,7 +702,7 @@ public struct Config: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum AddressMode: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum AddressMode: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -731,15 +734,9 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.NetworkConfig.AddressMode] = [ - .dhcp, - .static, - ] - } - public struct IpV4Config: Sendable { + public struct IpV4Config { // 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. @@ -772,7 +769,7 @@ public struct Config: Sendable { /// /// Display Config - public struct DisplayConfig: Sendable { + public struct DisplayConfig { // 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. @@ -828,7 +825,7 @@ public struct Config: Sendable { /// /// How the GPS coordinates are displayed on the OLED screen. - public enum GpsCoordinateFormat: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum GpsCoordinateFormat: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -891,21 +888,11 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [ - .dec, - .dms, - .utm, - .mgrs, - .olc, - .osgr, - ] - } /// /// Unit display preference - public enum DisplayUnits: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum DisplayUnits: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -937,17 +924,11 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.DisplayUnits] = [ - .metric, - .imperial, - ] - } /// /// Override OLED outo detect with this if it fails. - public enum OledType: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum OledType: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -991,17 +972,9 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.OledType] = [ - .oledAuto, - .oledSsd1306, - .oledSh1106, - .oledSh1107, - ] - } - public enum DisplayMode: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum DisplayMode: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1045,17 +1018,9 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.DisplayMode] = [ - .default, - .twocolor, - .inverted, - .color, - ] - } - public enum CompassOrientation: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum CompassOrientation: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1123,18 +1088,6 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.DisplayConfig.CompassOrientation] = [ - .degrees0, - .degrees90, - .degrees180, - .degrees270, - .degrees0Inverted, - .degrees90Inverted, - .degrees180Inverted, - .degrees270Inverted, - ] - } public init() {} @@ -1142,7 +1095,7 @@ public struct Config: Sendable { /// /// Lora Config - public struct LoRaConfig: @unchecked Sendable { + public struct LoRaConfig { // 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. @@ -1299,7 +1252,7 @@ public struct Config: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum RegionCode: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum RegionCode: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1433,35 +1386,12 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.LoRaConfig.RegionCode] = [ - .unset, - .us, - .eu433, - .eu868, - .cn, - .jp, - .anz, - .kr, - .tw, - .ru, - .in, - .nz865, - .th, - .lora24, - .ua433, - .ua868, - .my433, - .my919, - .sg923, - ] - } /// /// Standard predefined channel settings /// Note: these mappings must match ModemPreset Choice in the device code. - public enum ModemPreset: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum ModemPreset: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1474,6 +1404,7 @@ public struct Config: Sendable { /// /// Very Long Range - Slow + /// Deprecated in 2.5: Works only with txco and is unusably slow case veryLongSlow // = 2 /// @@ -1495,6 +1426,12 @@ public struct Config: Sendable { /// /// Long Range - Moderately Fast case longModerate // = 7 + + /// + /// Short Range - Turbo + /// This is the fastest preset and the only one with 500kHz bandwidth. + /// It is not legal to use in all regions due to this wider bandwidth. + case shortTurbo // = 8 case UNRECOGNIZED(Int) public init() { @@ -1511,6 +1448,7 @@ public struct Config: Sendable { case 5: self = .shortSlow case 6: self = .shortFast case 7: self = .longModerate + case 8: self = .shortTurbo default: self = .UNRECOGNIZED(rawValue) } } @@ -1525,22 +1463,11 @@ public struct Config: Sendable { case .shortSlow: return 5 case .shortFast: return 6 case .longModerate: return 7 + case .shortTurbo: return 8 case .UNRECOGNIZED(let i): return i } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.LoRaConfig.ModemPreset] = [ - .longFast, - .longSlow, - .veryLongSlow, - .mediumSlow, - .mediumFast, - .shortSlow, - .shortFast, - .longModerate, - ] - } public init() {} @@ -1548,7 +1475,7 @@ public struct Config: Sendable { fileprivate var _storage = _StorageClass.defaultInstance } - public struct BluetoothConfig: Sendable { + public struct BluetoothConfig { // 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. @@ -1567,11 +1494,12 @@ public struct Config: Sendable { /// /// Enables device (serial style logs) over Bluetooth + /// Moved to SecurityConfig public var deviceLoggingEnabled: Bool = false public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum PairingMode: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum PairingMode: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1609,21 +1537,255 @@ public struct Config: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Config.BluetoothConfig.PairingMode] = [ - .randomPin, - .fixedPin, - .noPin, - ] - } public init() {} } + public struct SecurityConfig { + // 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. + + /// + /// The public key of the user's device. + /// Sent out to other nodes on the mesh to allow them to compute a shared secret key. + public var publicKey: Data = Data() + + /// + /// The private key of the device. + /// Used to create a shared key with a remote device. + public var privateKey: Data = Data() + + /// + /// The public key authorized to send admin messages to this node. + public var adminKey: Data = Data() + + /// + /// If true, device is considered to be "managed" by a mesh administrator via admin messages + /// Device is managed by a mesh administrator. + public var isManaged: Bool = false + + /// + /// Serial Console over the Stream API." + public var serialEnabled: Bool = false + + /// + /// By default we turn off logging as soon as an API client connects (to keep shared serial link quiet). + /// Output live debug logging over serial. + public var debugLogApiEnabled: Bool = false + + /// + /// Enables device (serial style logs) over Bluetooth + public var bluetoothLoggingEnabled: Bool = false + + /// + /// Allow incoming device control over the insecure legacy admin channel. + public var adminChannelEnabled: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + } + public init() {} } +#if swift(>=4.2) + +extension Config.DeviceConfig.Role: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DeviceConfig.Role] = [ + .client, + .clientMute, + .router, + .routerClient, + .repeater, + .tracker, + .sensor, + .tak, + .clientHidden, + .lostAndFound, + .takTracker, + ] +} + +extension Config.DeviceConfig.RebroadcastMode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DeviceConfig.RebroadcastMode] = [ + .all, + .allSkipDecoding, + .localOnly, + .knownOnly, + ] +} + +extension Config.PositionConfig.PositionFlags: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.PositionConfig.PositionFlags] = [ + .unset, + .altitude, + .altitudeMsl, + .geoidalSeparation, + .dop, + .hvdop, + .satinview, + .seqNo, + .timestamp, + .heading, + .speed, + ] +} + +extension Config.PositionConfig.GpsMode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.PositionConfig.GpsMode] = [ + .disabled, + .enabled, + .notPresent, + ] +} + +extension Config.NetworkConfig.AddressMode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.NetworkConfig.AddressMode] = [ + .dhcp, + .static, + ] +} + +extension Config.DisplayConfig.GpsCoordinateFormat: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.GpsCoordinateFormat] = [ + .dec, + .dms, + .utm, + .mgrs, + .olc, + .osgr, + ] +} + +extension Config.DisplayConfig.DisplayUnits: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.DisplayUnits] = [ + .metric, + .imperial, + ] +} + +extension Config.DisplayConfig.OledType: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.OledType] = [ + .oledAuto, + .oledSsd1306, + .oledSh1106, + .oledSh1107, + ] +} + +extension Config.DisplayConfig.DisplayMode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.DisplayMode] = [ + .default, + .twocolor, + .inverted, + .color, + ] +} + +extension Config.DisplayConfig.CompassOrientation: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DisplayConfig.CompassOrientation] = [ + .degrees0, + .degrees90, + .degrees180, + .degrees270, + .degrees0Inverted, + .degrees90Inverted, + .degrees180Inverted, + .degrees270Inverted, + ] +} + +extension Config.LoRaConfig.RegionCode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.LoRaConfig.RegionCode] = [ + .unset, + .us, + .eu433, + .eu868, + .cn, + .jp, + .anz, + .kr, + .tw, + .ru, + .in, + .nz865, + .th, + .lora24, + .ua433, + .ua868, + .my433, + .my919, + .sg923, + ] +} + +extension Config.LoRaConfig.ModemPreset: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.LoRaConfig.ModemPreset] = [ + .longFast, + .longSlow, + .veryLongSlow, + .mediumSlow, + .mediumFast, + .shortSlow, + .shortFast, + .longModerate, + .shortTurbo, + ] +} + +extension Config.BluetoothConfig.PairingMode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.BluetoothConfig.PairingMode] = [ + .randomPin, + .fixedPin, + .noPin, + ] +} + +#endif // swift(>=4.2) + +#if swift(>=5.5) && canImport(_Concurrency) +extension Config: @unchecked Sendable {} +extension Config.OneOf_PayloadVariant: @unchecked Sendable {} +extension Config.DeviceConfig: @unchecked Sendable {} +extension Config.DeviceConfig.Role: @unchecked Sendable {} +extension Config.DeviceConfig.RebroadcastMode: @unchecked Sendable {} +extension Config.PositionConfig: @unchecked Sendable {} +extension Config.PositionConfig.PositionFlags: @unchecked Sendable {} +extension Config.PositionConfig.GpsMode: @unchecked Sendable {} +extension Config.PowerConfig: @unchecked Sendable {} +extension Config.NetworkConfig: @unchecked Sendable {} +extension Config.NetworkConfig.AddressMode: @unchecked Sendable {} +extension Config.NetworkConfig.IpV4Config: @unchecked Sendable {} +extension Config.DisplayConfig: @unchecked Sendable {} +extension Config.DisplayConfig.GpsCoordinateFormat: @unchecked Sendable {} +extension Config.DisplayConfig.DisplayUnits: @unchecked Sendable {} +extension Config.DisplayConfig.OledType: @unchecked Sendable {} +extension Config.DisplayConfig.DisplayMode: @unchecked Sendable {} +extension Config.DisplayConfig.CompassOrientation: @unchecked Sendable {} +extension Config.LoRaConfig: @unchecked Sendable {} +extension Config.LoRaConfig.RegionCode: @unchecked Sendable {} +extension Config.LoRaConfig.ModemPreset: @unchecked Sendable {} +extension Config.BluetoothConfig: @unchecked Sendable {} +extension Config.BluetoothConfig.PairingMode: @unchecked Sendable {} +extension Config.SecurityConfig: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -1638,6 +1800,7 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas 5: .same(proto: "display"), 6: .same(proto: "lora"), 7: .same(proto: "bluetooth"), + 8: .same(proto: "security"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -1737,6 +1900,19 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas self.payloadVariant = .bluetooth(v) } }() + case 8: try { + var v: Config.SecurityConfig? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .security(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .security(v) + } + }() default: break } } @@ -1776,6 +1952,10 @@ extension Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBas guard case .bluetooth(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 7) }() + case .security?: try { + guard case .security(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 8) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -2080,7 +2260,7 @@ extension Config.PowerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.onBatteryShutdownAfterSecs != 0 { try visitor.visitSingularUInt32Field(value: self.onBatteryShutdownAfterSecs, fieldNumber: 2) } - if self.adcMultiplierOverride.bitPattern != 0 { + if self.adcMultiplierOverride != 0 { try visitor.visitSingularFloatField(value: self.adcMultiplierOverride, fieldNumber: 3) } if self.waitBluetoothSecs != 0 { @@ -2524,7 +2704,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if _storage._codingRate != 0 { try visitor.visitSingularUInt32Field(value: _storage._codingRate, fieldNumber: 5) } - if _storage._frequencyOffset.bitPattern != 0 { + if _storage._frequencyOffset != 0 { try visitor.visitSingularFloatField(value: _storage._frequencyOffset, fieldNumber: 6) } if _storage._region != .unset { @@ -2548,7 +2728,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if _storage._sx126XRxBoostedGain != false { try visitor.visitSingularBoolField(value: _storage._sx126XRxBoostedGain, fieldNumber: 13) } - if _storage._overrideFrequency.bitPattern != 0 { + if _storage._overrideFrequency != 0 { try visitor.visitSingularFloatField(value: _storage._overrideFrequency, fieldNumber: 14) } if _storage._paFanDisabled != false { @@ -2629,6 +2809,7 @@ extension Config.LoRaConfig.ModemPreset: SwiftProtobuf._ProtoNameProviding { 5: .same(proto: "SHORT_SLOW"), 6: .same(proto: "SHORT_FAST"), 7: .same(proto: "LONG_MODERATE"), + 8: .same(proto: "SHORT_TURBO"), ] } @@ -2689,3 +2870,77 @@ extension Config.BluetoothConfig.PairingMode: SwiftProtobuf._ProtoNameProviding 2: .same(proto: "NO_PIN"), ] } + +extension Config.SecurityConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = Config.protoMessageName + ".SecurityConfig" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "public_key"), + 2: .standard(proto: "private_key"), + 3: .standard(proto: "admin_key"), + 4: .standard(proto: "is_managed"), + 5: .standard(proto: "serial_enabled"), + 6: .standard(proto: "debug_log_api_enabled"), + 7: .standard(proto: "bluetooth_logging_enabled"), + 8: .standard(proto: "admin_channel_enabled"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.publicKey) }() + case 2: try { try decoder.decodeSingularBytesField(value: &self.privateKey) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self.adminKey) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self.isManaged) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self.serialEnabled) }() + case 6: try { try decoder.decodeSingularBoolField(value: &self.debugLogApiEnabled) }() + case 7: try { try decoder.decodeSingularBoolField(value: &self.bluetoothLoggingEnabled) }() + case 8: try { try decoder.decodeSingularBoolField(value: &self.adminChannelEnabled) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.publicKey.isEmpty { + try visitor.visitSingularBytesField(value: self.publicKey, fieldNumber: 1) + } + if !self.privateKey.isEmpty { + try visitor.visitSingularBytesField(value: self.privateKey, fieldNumber: 2) + } + if !self.adminKey.isEmpty { + try visitor.visitSingularBytesField(value: self.adminKey, fieldNumber: 3) + } + if self.isManaged != false { + try visitor.visitSingularBoolField(value: self.isManaged, fieldNumber: 4) + } + if self.serialEnabled != false { + try visitor.visitSingularBoolField(value: self.serialEnabled, fieldNumber: 5) + } + if self.debugLogApiEnabled != false { + try visitor.visitSingularBoolField(value: self.debugLogApiEnabled, fieldNumber: 6) + } + if self.bluetoothLoggingEnabled != false { + try visitor.visitSingularBoolField(value: self.bluetoothLoggingEnabled, fieldNumber: 7) + } + if self.adminChannelEnabled != false { + try visitor.visitSingularBoolField(value: self.adminChannelEnabled, fieldNumber: 8) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: Config.SecurityConfig, rhs: Config.SecurityConfig) -> Bool { + if lhs.publicKey != rhs.publicKey {return false} + if lhs.privateKey != rhs.privateKey {return false} + if lhs.adminKey != rhs.adminKey {return false} + if lhs.isManaged != rhs.isManaged {return false} + if lhs.serialEnabled != rhs.serialEnabled {return false} + if lhs.debugLogApiEnabled != rhs.debugLogApiEnabled {return false} + if lhs.bluetoothLoggingEnabled != rhs.bluetoothLoggingEnabled {return false} + if lhs.adminChannelEnabled != rhs.adminChannelEnabled {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift index a4569714..a2ec180e 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/connection_status.pb.swift @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct DeviceConnectionStatus: Sendable { +public struct DeviceConnectionStatus { // 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. @@ -81,7 +81,7 @@ public struct DeviceConnectionStatus: Sendable { /// /// WiFi connection status -public struct WifiConnectionStatus: Sendable { +public struct WifiConnectionStatus { // 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. @@ -114,7 +114,7 @@ public struct WifiConnectionStatus: Sendable { /// /// Ethernet connection status -public struct EthernetConnectionStatus: Sendable { +public struct EthernetConnectionStatus { // 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. @@ -139,7 +139,7 @@ public struct EthernetConnectionStatus: Sendable { /// /// Ethernet or WiFi connection status -public struct NetworkConnectionStatus: Sendable { +public struct NetworkConnectionStatus { // 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. @@ -167,7 +167,7 @@ public struct NetworkConnectionStatus: Sendable { /// /// Bluetooth connection status -public struct BluetoothConnectionStatus: Sendable { +public struct BluetoothConnectionStatus { // 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. @@ -191,7 +191,7 @@ public struct BluetoothConnectionStatus: Sendable { /// /// Serial connection status -public struct SerialConnectionStatus: Sendable { +public struct SerialConnectionStatus { // 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. @@ -209,6 +209,15 @@ public struct SerialConnectionStatus: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension DeviceConnectionStatus: @unchecked Sendable {} +extension WifiConnectionStatus: @unchecked Sendable {} +extension EthernetConnectionStatus: @unchecked Sendable {} +extension NetworkConnectionStatus: @unchecked Sendable {} +extension BluetoothConnectionStatus: @unchecked Sendable {} +extension SerialConnectionStatus: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift index 834f9636..43506399 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// Font sizes for the device screen -public enum ScreenFonts: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum ScreenFonts: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -60,18 +60,24 @@ public enum ScreenFonts: SwiftProtobuf.Enum, Swift.CaseIterable { } } +} + +#if swift(>=4.2) + +extension ScreenFonts: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [ScreenFonts] = [ .fontSmall, .fontMedium, .fontLarge, ] - } +#endif // swift(>=4.2) + /// /// Position with static location information only for NodeDBLite -public struct PositionLite: Sendable { +public struct PositionLite { // 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. @@ -106,7 +112,52 @@ public struct PositionLite: Sendable { public init() {} } -public struct NodeInfoLite: @unchecked Sendable { +public struct UserLite { + // 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. + + /// + /// This is the addr of the radio. + public var macaddr: Data = Data() + + /// + /// A full name for this user, i.e. "Kevin Hester" + public var longName: String = String() + + /// + /// A VERY short name, ideally two characters. + /// Suitable for a tiny OLED screen + public var shortName: String = String() + + /// + /// TBEAM, HELTEC, etc... + /// Starting in 1.2.11 moved to hw_model enum in the NodeInfo object. + /// Apps will still need the string here for older builds + /// (so OTA update can find the right image), but if the enum is available it will be used instead. + public var hwModel: HardwareModel = .unset + + /// + /// In some regions Ham radio operators have different bandwidth limitations than others. + /// If this user is a licensed operator, set this flag. + /// Also, "long_name" should be their licence number. + public var isLicensed: Bool = false + + /// + /// Indicates that the user's role in the mesh + public var role: Config.DeviceConfig.Role = .client + + /// + /// The public key of the user's device. + /// This is sent out to other nodes on the mesh to allow them to compute a shared secret key. + public var publicKey: Data = Data() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct NodeInfoLite { // 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. @@ -120,8 +171,8 @@ public struct NodeInfoLite: @unchecked Sendable { /// /// The user info for this node - public var user: User { - get {return _storage._user ?? User()} + public var user: UserLite { + get {return _storage._user ?? UserLite()} set {_uniqueStorage()._user = newValue} } /// Returns true if `user` has been explicitly set. @@ -209,7 +260,7 @@ public struct NodeInfoLite: @unchecked Sendable { /// FIXME, since we write this each time we enter deep sleep (and have infinite /// flash) it would be better to use some sort of append only data structure for /// the receive queue and use the preferences store for the other stuff -public struct DeviceState: @unchecked Sendable { +public struct DeviceState { // 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. @@ -269,8 +320,6 @@ public struct DeviceState: @unchecked Sendable { /// Used only during development. /// Indicates developer is testing and changes should never be saved to flash. /// Deprecated in 2.3.1 - /// - /// NOTE: This field was marked as deprecated in the .proto file. public var noSave: Bool { get {return _storage._noSave} set {_uniqueStorage()._noSave = newValue} @@ -319,7 +368,7 @@ public struct DeviceState: @unchecked Sendable { /// /// The on-disk saved channels -public struct ChannelFile: Sendable { +public struct ChannelFile { // 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. @@ -342,7 +391,7 @@ public struct ChannelFile: Sendable { /// /// This can be used for customizing the firmware distribution. If populated, /// show a secondary bootup screen with custom logo and text for 2.5 seconds. -public struct OEMStore: @unchecked Sendable { +public struct OEMStore { // 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. @@ -401,6 +450,16 @@ public struct OEMStore: @unchecked Sendable { fileprivate var _oemLocalModuleConfig: LocalModuleConfig? = nil } +#if swift(>=5.5) && canImport(_Concurrency) +extension ScreenFonts: @unchecked Sendable {} +extension PositionLite: @unchecked Sendable {} +extension UserLite: @unchecked Sendable {} +extension NodeInfoLite: @unchecked Sendable {} +extension DeviceState: @unchecked Sendable {} +extension ChannelFile: @unchecked Sendable {} +extension OEMStore: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -469,6 +528,74 @@ extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat } } +extension UserLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".UserLite" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "macaddr"), + 2: .standard(proto: "long_name"), + 3: .standard(proto: "short_name"), + 4: .standard(proto: "hw_model"), + 5: .standard(proto: "is_licensed"), + 6: .same(proto: "role"), + 7: .standard(proto: "public_key"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self.macaddr) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.longName) }() + case 3: try { try decoder.decodeSingularStringField(value: &self.shortName) }() + case 4: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self.isLicensed) }() + case 6: try { try decoder.decodeSingularEnumField(value: &self.role) }() + case 7: try { try decoder.decodeSingularBytesField(value: &self.publicKey) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.macaddr.isEmpty { + try visitor.visitSingularBytesField(value: self.macaddr, fieldNumber: 1) + } + if !self.longName.isEmpty { + try visitor.visitSingularStringField(value: self.longName, fieldNumber: 2) + } + if !self.shortName.isEmpty { + try visitor.visitSingularStringField(value: self.shortName, fieldNumber: 3) + } + if self.hwModel != .unset { + try visitor.visitSingularEnumField(value: self.hwModel, fieldNumber: 4) + } + if self.isLicensed != false { + try visitor.visitSingularBoolField(value: self.isLicensed, fieldNumber: 5) + } + if self.role != .client { + try visitor.visitSingularEnumField(value: self.role, fieldNumber: 6) + } + if !self.publicKey.isEmpty { + try visitor.visitSingularBytesField(value: self.publicKey, fieldNumber: 7) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: UserLite, rhs: UserLite) -> Bool { + if lhs.macaddr != rhs.macaddr {return false} + if lhs.longName != rhs.longName {return false} + if lhs.shortName != rhs.shortName {return false} + if lhs.hwModel != rhs.hwModel {return false} + if lhs.isLicensed != rhs.isLicensed {return false} + if lhs.role != rhs.role {return false} + if lhs.publicKey != rhs.publicKey {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".NodeInfoLite" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -486,7 +613,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat fileprivate class _StorageClass { var _num: UInt32 = 0 - var _user: User? = nil + var _user: UserLite? = nil var _position: PositionLite? = nil var _snr: Float = 0 var _lastHeard: UInt32 = 0 @@ -568,7 +695,7 @@ extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat try { if let v = _storage._position { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if _storage._snr.bitPattern != 0 { + if _storage._snr != 0 { try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) } if _storage._lastHeard != 0 { diff --git a/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift index 17dd5baa..0af27466 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/localonly.pb.swift @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct LocalConfig: @unchecked Sendable { +public struct LocalConfig { // 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. @@ -111,6 +111,17 @@ public struct LocalConfig: @unchecked Sendable { set {_uniqueStorage()._version = newValue} } + /// + /// The part of the config that is specific to Security settings + public var security: Config.SecurityConfig { + get {return _storage._security ?? Config.SecurityConfig()} + set {_uniqueStorage()._security = newValue} + } + /// Returns true if `security` has been explicitly set. + public var hasSecurity: Bool {return _storage._security != nil} + /// Clears the value of `security`. Subsequent reads from it will return its default value. + public mutating func clearSecurity() {_uniqueStorage()._security = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -118,7 +129,7 @@ public struct LocalConfig: @unchecked Sendable { fileprivate var _storage = _StorageClass.defaultInstance } -public struct LocalModuleConfig: @unchecked Sendable { +public struct LocalModuleConfig { // 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. @@ -282,6 +293,11 @@ public struct LocalModuleConfig: @unchecked Sendable { fileprivate var _storage = _StorageClass.defaultInstance } +#if swift(>=5.5) && canImport(_Concurrency) +extension LocalConfig: @unchecked Sendable {} +extension LocalModuleConfig: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -297,6 +313,7 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati 6: .same(proto: "lora"), 7: .same(proto: "bluetooth"), 8: .same(proto: "version"), + 9: .same(proto: "security"), ] fileprivate class _StorageClass { @@ -308,6 +325,7 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati var _lora: Config.LoRaConfig? = nil var _bluetooth: Config.BluetoothConfig? = nil var _version: UInt32 = 0 + var _security: Config.SecurityConfig? = nil #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -330,6 +348,7 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati _lora = source._lora _bluetooth = source._bluetooth _version = source._version + _security = source._security } } @@ -356,6 +375,7 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati case 6: try { try decoder.decodeSingularMessageField(value: &_storage._lora) }() case 7: try { try decoder.decodeSingularMessageField(value: &_storage._bluetooth) }() case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }() + case 9: try { try decoder.decodeSingularMessageField(value: &_storage._security) }() default: break } } @@ -392,6 +412,9 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati if _storage._version != 0 { try visitor.visitSingularUInt32Field(value: _storage._version, fieldNumber: 8) } + try { if let v = _storage._security { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -409,6 +432,7 @@ extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati if _storage._lora != rhs_storage._lora {return false} if _storage._bluetooth != rhs_storage._bluetooth {return false} if _storage._version != rhs_storage._version {return false} + if _storage._security != rhs_storage._security {return false} return true } if !storagesAreEqual {return false} diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index 996e8268..ba12908d 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -25,7 +25,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// bin/build-all.sh script. /// Because they will be used to find firmware filenames in the android app for OTA updates. /// To match the old style filenames, _ is converted to -, p is converted to . -public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum HardwareModel: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -329,6 +329,23 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { /// Seeed studio T1000-E tracker card. NRF52840 w/ LR1110 radio, GPS, button, buzzer, and sensors. case trackerT1000E // = 71 + /// + /// RAK3172 STM32WLE5 Module (https://store.rakwireless.com/products/wisduo-lpwan-module-rak3172) + case rak3172 // = 72 + + /// + /// Seeed Studio Wio-E5 (either mini or Dev kit) using STM32WL chip. + case wioE5 // = 73 + + /// + /// RadioMaster 900 Bandit, https://www.radiomasterrc.com/products/bandit-expresslrs-rf-module + /// SSD1306 OLED and No GPS + case radiomaster900Bandit // = 74 + + /// + /// Minewsemi ME25LS01 (ME25LE01_V1.0). NRF52840 w/ LR1110 radio, buttons and leds and pins. + case me25Ls014Y10Td // = 75 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. @@ -413,6 +430,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { case 69: self = .heltecMeshNodeT114 case 70: self = .sensecapIndicator case 71: self = .trackerT1000E + case 72: self = .rak3172 + case 73: self = .wioE5 + case 74: self = .radiomaster900Bandit + case 75: self = .me25Ls014Y10Td case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -491,11 +512,20 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { case .heltecMeshNodeT114: return 69 case .sensecapIndicator: return 70 case .trackerT1000E: return 71 + case .rak3172: return 72 + case .wioE5: return 73 + case .radiomaster900Bandit: return 74 + case .me25Ls014Y10Td: return 75 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } } +} + +#if swift(>=4.2) + +extension HardwareModel: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [HardwareModel] = [ .unset, @@ -569,14 +599,19 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { .heltecMeshNodeT114, .sensecapIndicator, .trackerT1000E, + .rak3172, + .wioE5, + .radiomaster900Bandit, + .me25Ls014Y10Td, .privateHw, ] - } +#endif // swift(>=4.2) + /// /// Shared constants between device and phone -public enum Constants: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum Constants: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -611,20 +646,26 @@ public enum Constants: SwiftProtobuf.Enum, Swift.CaseIterable { } } +} + +#if swift(>=4.2) + +extension Constants: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [Constants] = [ .zero, .dataPayloadLen, ] - } +#endif // swift(>=4.2) + /// /// Error codes for critical errors /// The device might report these fault codes on the screen. /// If you encounter a fault code, please post on the meshtastic.discourse.group /// and we'll try to help. -public enum CriticalErrorCode: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum CriticalErrorCode: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -676,6 +717,17 @@ public enum CriticalErrorCode: SwiftProtobuf.Enum, Swift.CaseIterable { /// A (likely software but possibly hardware) failure was detected while trying to send packets. /// If this occurs on your board, please post in the forum so that we can ask you to collect some information to allow fixing this bug case radioSpiBug // = 11 + + /// + /// Corruption was detected on the flash filesystem but we were able to repair things. + /// If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field. + case flashCorruptionRecoverable // = 12 + + /// + /// Corruption was detected on the flash filesystem but we were unable to repair things. + /// NOTE: Your node will probably need to be reconfigured the next time it reboots (it will lose the region code etc...) + /// If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field. + case flashCorruptionUnrecoverable // = 13 case UNRECOGNIZED(Int) public init() { @@ -696,6 +748,8 @@ public enum CriticalErrorCode: SwiftProtobuf.Enum, Swift.CaseIterable { case 9: self = .brownout case 10: self = .sx1262Failure case 11: self = .radioSpiBug + case 12: self = .flashCorruptionRecoverable + case 13: self = .flashCorruptionUnrecoverable default: self = .UNRECOGNIZED(rawValue) } } @@ -714,10 +768,17 @@ public enum CriticalErrorCode: SwiftProtobuf.Enum, Swift.CaseIterable { case .brownout: return 9 case .sx1262Failure: return 10 case .radioSpiBug: return 11 + case .flashCorruptionRecoverable: return 12 + case .flashCorruptionUnrecoverable: return 13 case .UNRECOGNIZED(let i): return i } } +} + +#if swift(>=4.2) + +extension CriticalErrorCode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [CriticalErrorCode] = [ .none, @@ -732,13 +793,16 @@ public enum CriticalErrorCode: SwiftProtobuf.Enum, Swift.CaseIterable { .brownout, .sx1262Failure, .radioSpiBug, + .flashCorruptionRecoverable, + .flashCorruptionUnrecoverable, ] - } +#endif // swift(>=4.2) + /// /// a gps position -public struct Position: @unchecked Sendable { +public struct Position { // 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. @@ -747,23 +811,35 @@ public struct Position: @unchecked Sendable { /// The new preferred location encoding, multiply by 1e-7 to get degrees /// in floating point public var latitudeI: Int32 { - get {return _storage._latitudeI} + get {return _storage._latitudeI ?? 0} set {_uniqueStorage()._latitudeI = newValue} } + /// Returns true if `latitudeI` has been explicitly set. + public var hasLatitudeI: Bool {return _storage._latitudeI != nil} + /// Clears the value of `latitudeI`. Subsequent reads from it will return its default value. + public mutating func clearLatitudeI() {_uniqueStorage()._latitudeI = nil} /// /// TODO: REPLACE public var longitudeI: Int32 { - get {return _storage._longitudeI} + get {return _storage._longitudeI ?? 0} set {_uniqueStorage()._longitudeI = newValue} } + /// Returns true if `longitudeI` has been explicitly set. + public var hasLongitudeI: Bool {return _storage._longitudeI != nil} + /// Clears the value of `longitudeI`. Subsequent reads from it will return its default value. + public mutating func clearLongitudeI() {_uniqueStorage()._longitudeI = nil} /// /// In meters above MSL (but see issue #359) public var altitude: Int32 { - get {return _storage._altitude} + get {return _storage._altitude ?? 0} set {_uniqueStorage()._altitude = newValue} } + /// Returns true if `altitude` has been explicitly set. + public var hasAltitude: Bool {return _storage._altitude != nil} + /// Clears the value of `altitude`. Subsequent reads from it will return its default value. + public mutating func clearAltitude() {_uniqueStorage()._altitude = nil} /// /// This is usually not sent over the mesh (to save space), but it is sent @@ -806,16 +882,24 @@ public struct Position: @unchecked Sendable { /// /// HAE altitude in meters - can be used instead of MSL altitude public var altitudeHae: Int32 { - get {return _storage._altitudeHae} + get {return _storage._altitudeHae ?? 0} set {_uniqueStorage()._altitudeHae = newValue} } + /// Returns true if `altitudeHae` has been explicitly set. + public var hasAltitudeHae: Bool {return _storage._altitudeHae != nil} + /// Clears the value of `altitudeHae`. Subsequent reads from it will return its default value. + public mutating func clearAltitudeHae() {_uniqueStorage()._altitudeHae = nil} /// /// Geoidal separation in meters public var altitudeGeoidalSeparation: Int32 { - get {return _storage._altitudeGeoidalSeparation} + get {return _storage._altitudeGeoidalSeparation ?? 0} set {_uniqueStorage()._altitudeGeoidalSeparation = newValue} } + /// Returns true if `altitudeGeoidalSeparation` has been explicitly set. + public var hasAltitudeGeoidalSeparation: Bool {return _storage._altitudeGeoidalSeparation != nil} + /// Clears the value of `altitudeGeoidalSeparation`. Subsequent reads from it will return its default value. + public mutating func clearAltitudeGeoidalSeparation() {_uniqueStorage()._altitudeGeoidalSeparation = nil} /// /// Horizontal, Vertical and Position Dilution of Precision, in 1/100 units @@ -859,16 +943,24 @@ public struct Position: @unchecked Sendable { /// - "yaw" indicates a relative rotation about the vertical axis /// TODO: REMOVE/INTEGRATE public var groundSpeed: UInt32 { - get {return _storage._groundSpeed} + get {return _storage._groundSpeed ?? 0} set {_uniqueStorage()._groundSpeed = newValue} } + /// Returns true if `groundSpeed` has been explicitly set. + public var hasGroundSpeed: Bool {return _storage._groundSpeed != nil} + /// Clears the value of `groundSpeed`. Subsequent reads from it will return its default value. + public mutating func clearGroundSpeed() {_uniqueStorage()._groundSpeed = nil} /// /// TODO: REPLACE public var groundTrack: UInt32 { - get {return _storage._groundTrack} + get {return _storage._groundTrack ?? 0} set {_uniqueStorage()._groundTrack = newValue} } + /// Returns true if `groundTrack` has been explicitly set. + public var hasGroundTrack: Bool {return _storage._groundTrack != nil} + /// Clears the value of `groundTrack`. Subsequent reads from it will return its default value. + public mutating func clearGroundTrack() {_uniqueStorage()._groundTrack = nil} /// /// GPS fix quality (from NMEA GxGGA statement or similar) @@ -927,7 +1019,7 @@ public struct Position: @unchecked Sendable { /// /// How the location was acquired: manual, onboard GPS, external (EUD) GPS - public enum LocSource: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum LocSource: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -971,20 +1063,12 @@ public struct Position: @unchecked Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Position.LocSource] = [ - .locUnset, - .locManual, - .locInternal, - .locExternal, - ] - } /// /// How the altitude was acquired: manual, GPS int/ext, etc /// Default: same as location_source if present - public enum AltSource: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum AltSource: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1034,15 +1118,6 @@ public struct Position: @unchecked Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Position.AltSource] = [ - .altUnset, - .altManual, - .altInternal, - .altExternal, - .altBarometric, - ] - } public init() {} @@ -1050,6 +1125,31 @@ public struct Position: @unchecked Sendable { fileprivate var _storage = _StorageClass.defaultInstance } +#if swift(>=4.2) + +extension Position.LocSource: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Position.LocSource] = [ + .locUnset, + .locManual, + .locInternal, + .locExternal, + ] +} + +extension Position.AltSource: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Position.AltSource] = [ + .altUnset, + .altManual, + .altInternal, + .altExternal, + .altBarometric, + ] +} + +#endif // swift(>=4.2) + /// /// Broadcast when a newly powered mesh node wants to find a node num it can use /// Sent from the phone over bluetooth to set the user id for the owner of this node. @@ -1071,7 +1171,7 @@ public struct Position: @unchecked Sendable { /// A few nodenums are reserved and will never be requested: /// 0xff - broadcast /// 0 through 3 - for future use -public struct User: @unchecked Sendable { +public struct User { // 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. @@ -1096,8 +1196,6 @@ public struct User: @unchecked Sendable { /// Deprecated in Meshtastic 2.1.x /// This is the addr of the radio. /// Not populated by the phone, but added by the esp32 when broadcasting - /// - /// NOTE: This field was marked as deprecated in the .proto file. public var macaddr: Data = Data() /// @@ -1117,22 +1215,39 @@ public struct User: @unchecked Sendable { /// Indicates that the user's role in the mesh public var role: Config.DeviceConfig.Role = .client + /// + /// The public key of the user's device. + /// This is sent out to other nodes on the mesh to allow them to compute a shared secret key. + public var publicKey: Data = Data() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} } /// -/// A message used in our Dynamic Source Routing protocol (RFC 4728 based) -public struct RouteDiscovery: Sendable { +/// A message used in a traceroute +public struct RouteDiscovery { // 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. /// - /// The list of nodenums this packet has visited so far + /// The list of nodenums this packet has visited so far to the destination. public var route: [UInt32] = [] + /// + /// The list of SNRs (in dB, scaled by 4) in the route towards the destination. + public var snrTowards: [Int32] = [] + + /// + /// The list of nodenums the packet has visited on the way back from the destination. + public var routeBack: [UInt32] = [] + + /// + /// The list of SNRs (in dB, scaled by 4) in the route back from the destination. + public var snrBack: [Int32] = [] + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -1140,7 +1255,7 @@ public struct RouteDiscovery: Sendable { /// /// A Routing control Data packet handled by the routing module -public struct Routing: Sendable { +public struct Routing { // 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. @@ -1180,7 +1295,7 @@ public struct Routing: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Variant: Equatable, Sendable { + public enum OneOf_Variant: Equatable { /// /// A route request going from the requester case routeRequest(RouteDiscovery) @@ -1192,12 +1307,34 @@ public struct Routing: Sendable { /// in addition to ack.fail_id to provide details on the type of failure). case errorReason(Routing.Error) + #if !swift(>=4.1) + public static func ==(lhs: Routing.OneOf_Variant, rhs: Routing.OneOf_Variant) -> Bool { + // 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 (lhs, rhs) { + case (.routeRequest, .routeRequest): return { + guard case .routeRequest(let l) = lhs, case .routeRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.routeReply, .routeReply): return { + guard case .routeReply(let l) = lhs, case .routeReply(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.errorReason, .errorReason): return { + guard case .errorReason(let l) = lhs, case .errorReason(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } /// /// A failure in delivering a message (usually used for routing control messages, but might be provided in addition to ack.fail_id to provide /// details on the type of failure). - public enum Error: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Error: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1249,6 +1386,14 @@ public struct Routing: Sendable { /// The application layer service on the remote node received your request, but considered your request not authorized /// (i.e you did not send the request on the required bound channel) case notAuthorized // = 33 + + /// + /// The client specified a PKI transport, but the node was unable to send the packet using PKI (and did not send the message at all) + case pkiFailed // = 34 + + /// + /// The receiving node does not have a Public Key to decode with + case pkiUnknownPubkey // = 35 case UNRECOGNIZED(Int) public init() { @@ -1269,6 +1414,8 @@ public struct Routing: Sendable { case 9: self = .dutyCycleLimit case 32: self = .badRequest case 33: self = .notAuthorized + case 34: self = .pkiFailed + case 35: self = .pkiUnknownPubkey default: self = .UNRECOGNIZED(rawValue) } } @@ -1287,36 +1434,46 @@ public struct Routing: Sendable { case .dutyCycleLimit: return 9 case .badRequest: return 32 case .notAuthorized: return 33 + case .pkiFailed: return 34 + case .pkiUnknownPubkey: return 35 case .UNRECOGNIZED(let i): return i } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Routing.Error] = [ - .none, - .noRoute, - .gotNak, - .timeout, - .noInterface, - .maxRetransmit, - .noChannel, - .tooLarge, - .noResponse, - .dutyCycleLimit, - .badRequest, - .notAuthorized, - ] - } public init() {} } +#if swift(>=4.2) + +extension Routing.Error: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Routing.Error] = [ + .none, + .noRoute, + .gotNak, + .timeout, + .noInterface, + .maxRetransmit, + .noChannel, + .tooLarge, + .noResponse, + .dutyCycleLimit, + .badRequest, + .notAuthorized, + .pkiFailed, + .pkiUnknownPubkey, + ] +} + +#endif // swift(>=4.2) + /// /// (Formerly called SubPacket) /// The payload portion fo a packet, this is the actual bytes that are sent /// inside a radio packet (because from/to are broken out by the comms library) -public struct DataMessage: @unchecked Sendable { +public struct DataMessage { // 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. @@ -1370,7 +1527,7 @@ public struct DataMessage: @unchecked Sendable { /// /// Waypoint message, used to share arbitrary locations across the mesh -public struct Waypoint: Sendable { +public struct Waypoint { // 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. @@ -1381,11 +1538,25 @@ public struct Waypoint: Sendable { /// /// latitude_i - public var latitudeI: Int32 = 0 + public var latitudeI: Int32 { + get {return _latitudeI ?? 0} + set {_latitudeI = newValue} + } + /// Returns true if `latitudeI` has been explicitly set. + public var hasLatitudeI: Bool {return self._latitudeI != nil} + /// Clears the value of `latitudeI`. Subsequent reads from it will return its default value. + public mutating func clearLatitudeI() {self._latitudeI = nil} /// /// longitude_i - public var longitudeI: Int32 = 0 + public var longitudeI: Int32 { + get {return _longitudeI ?? 0} + set {_longitudeI = newValue} + } + /// Returns true if `longitudeI` has been explicitly set. + public var hasLongitudeI: Bool {return self._longitudeI != nil} + /// Clears the value of `longitudeI`. Subsequent reads from it will return its default value. + public mutating func clearLongitudeI() {self._longitudeI = nil} /// /// Time the waypoint is to expire (epoch) @@ -1411,11 +1582,14 @@ public struct Waypoint: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _latitudeI: Int32? = nil + fileprivate var _longitudeI: Int32? = nil } /// /// This message will be proxied over the PhoneAPI for the client to deliver to the MQTT server -public struct MqttClientProxyMessage: @unchecked Sendable { +public struct MqttClientProxyMessage { // 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. @@ -1456,7 +1630,7 @@ public struct MqttClientProxyMessage: @unchecked Sendable { /// /// The actual service envelope payload or text for mqtt pub / sub - public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable { + public enum OneOf_PayloadVariant: Equatable { /// /// Bytes case data(Data) @@ -1464,6 +1638,24 @@ public struct MqttClientProxyMessage: @unchecked Sendable { /// Text case text(String) + #if !swift(>=4.1) + public static func ==(lhs: MqttClientProxyMessage.OneOf_PayloadVariant, rhs: MqttClientProxyMessage.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.data, .data): return { + guard case .data(let l) = lhs, case .data(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.text, .text): return { + guard case .text(let l) = lhs, case .text(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } public init() {} @@ -1473,7 +1665,7 @@ public struct MqttClientProxyMessage: @unchecked Sendable { /// A packet envelope sent/received over the mesh /// only payload_variant is sent in the payload portion of the LORA packet. /// The other fields are either not sent at all, or sent in the special 16 byte LORA header. -public struct MeshPacket: @unchecked Sendable { +public struct MeshPacket { // 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. @@ -1607,8 +1799,6 @@ public struct MeshPacket: @unchecked Sendable { /// /// Describe if this message is delayed - /// - /// NOTE: This field was marked as deprecated in the .proto file. public var delayed: MeshPacket.Delayed { get {return _storage._delayed} set {_uniqueStorage()._delayed = newValue} @@ -1629,9 +1819,23 @@ public struct MeshPacket: @unchecked Sendable { set {_uniqueStorage()._hopStart = newValue} } + /// + /// Records the public key the packet was encrypted with, if applicable. + public var publicKey: Data { + get {return _storage._publicKey} + set {_uniqueStorage()._publicKey = newValue} + } + + /// + /// Indicates whether the packet was en/decrypted using PKI + public var pkiEncrypted: Bool { + get {return _storage._pkiEncrypted} + set {_uniqueStorage()._pkiEncrypted = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_PayloadVariant: Equatable, @unchecked Sendable { + public enum OneOf_PayloadVariant: Equatable { /// /// TODO: REPLACE case decoded(DataMessage) @@ -1639,6 +1843,24 @@ public struct MeshPacket: @unchecked Sendable { /// TODO: REPLACE case encrypted(Data) + #if !swift(>=4.1) + public static func ==(lhs: MeshPacket.OneOf_PayloadVariant, rhs: MeshPacket.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.decoded, .decoded): return { + guard case .decoded(let l) = lhs, case .decoded(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.encrypted, .encrypted): return { + guard case .encrypted(let l) = lhs, case .encrypted(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } /// @@ -1660,7 +1882,7 @@ public struct MeshPacket: @unchecked Sendable { /// So I bit the bullet and implemented a new (internal - not sent over the air) /// field in MeshPacket called 'priority'. /// And the transmission queue in the router object is now a priority queue. - public enum Priority: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Priority: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1725,22 +1947,11 @@ public struct MeshPacket: @unchecked Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [MeshPacket.Priority] = [ - .unset, - .min, - .background, - .default, - .reliable, - .ack, - .max, - ] - } /// /// Identify if this is a delayed packet - public enum Delayed: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Delayed: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1778,13 +1989,6 @@ public struct MeshPacket: @unchecked Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [MeshPacket.Delayed] = [ - .noDelay, - .broadcast, - .direct, - ] - } public init() {} @@ -1792,6 +1996,32 @@ public struct MeshPacket: @unchecked Sendable { fileprivate var _storage = _StorageClass.defaultInstance } +#if swift(>=4.2) + +extension MeshPacket.Priority: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [MeshPacket.Priority] = [ + .unset, + .min, + .background, + .default, + .reliable, + .ack, + .max, + ] +} + +extension MeshPacket.Delayed: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [MeshPacket.Delayed] = [ + .noDelay, + .broadcast, + .direct, + ] +} + +#endif // swift(>=4.2) + /// /// The bluetooth to device link: /// Old BTLE protocol docs from TODO, merge in above and make real docs... @@ -1809,7 +2039,7 @@ public struct MeshPacket: @unchecked Sendable { /// level etc) SET_CONFIG (switches device to a new set of radio params and /// preshared key, drops all existing nodes, force our node to rejoin this new group) /// Full information about a node on the mesh -public struct NodeInfo: @unchecked Sendable { +public struct NodeInfo { // 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. @@ -1910,7 +2140,7 @@ public struct NodeInfo: @unchecked Sendable { /// Unique local debugging info for this node /// Note: we don't include position or the user info, because that will come in the /// Sent to the phone in response to WantNodes. -public struct MyNodeInfo: Sendable { +public struct MyNodeInfo { // 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. @@ -1941,7 +2171,7 @@ public struct MyNodeInfo: Sendable { /// on the message it is assumed to be a continuation of the previously sent message. /// This allows the device code to use fixed maxlen 64 byte strings for messages, /// and then extend as needed by emitting multiple records. -public struct LogRecord: Sendable { +public struct LogRecord { // 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. @@ -1966,7 +2196,7 @@ public struct LogRecord: Sendable { /// /// Log levels, chosen to match python logging conventions. - public enum Level: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Level: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -2028,23 +2258,29 @@ public struct LogRecord: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [LogRecord.Level] = [ - .unset, - .critical, - .error, - .warning, - .info, - .debug, - .trace, - ] - } public init() {} } -public struct QueueStatus: Sendable { +#if swift(>=4.2) + +extension LogRecord.Level: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [LogRecord.Level] = [ + .unset, + .critical, + .error, + .warning, + .info, + .debug, + .trace, + ] +} + +#endif // swift(>=4.2) + +public struct QueueStatus { // 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. @@ -2071,7 +2307,7 @@ public struct QueueStatus: Sendable { /// It will support READ and NOTIFY. When a new packet arrives the device will BLE notify? /// It will sit in that descriptor until consumed by the phone, /// at which point the next item in the FIFO will be populated. -public struct FromRadio: Sendable { +public struct FromRadio { // 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. @@ -2233,11 +2469,21 @@ public struct FromRadio: Sendable { set {payloadVariant = .fileInfo(newValue)} } + /// + /// Notification message to the client + public var clientNotification: ClientNotification { + get { + if case .clientNotification(let v)? = payloadVariant {return v} + return ClientNotification() + } + set {payloadVariant = .clientNotification(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() /// /// Log levels, chosen to match python logging conventions. - public enum OneOf_PayloadVariant: Equatable, Sendable { + public enum OneOf_PayloadVariant: Equatable { /// /// Log levels, chosen to match python logging conventions. case packet(MeshPacket) @@ -2288,15 +2534,128 @@ public struct FromRadio: Sendable { /// /// File system manifest messages case fileInfo(FileInfo) + /// + /// Notification message to the client + case clientNotification(ClientNotification) + #if !swift(>=4.1) + public static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.packet, .packet): return { + guard case .packet(let l) = lhs, case .packet(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.myInfo, .myInfo): return { + guard case .myInfo(let l) = lhs, case .myInfo(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.nodeInfo, .nodeInfo): return { + guard case .nodeInfo(let l) = lhs, case .nodeInfo(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.config, .config): return { + guard case .config(let l) = lhs, case .config(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.logRecord, .logRecord): return { + guard case .logRecord(let l) = lhs, case .logRecord(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.configCompleteID, .configCompleteID): return { + guard case .configCompleteID(let l) = lhs, case .configCompleteID(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.rebooted, .rebooted): return { + guard case .rebooted(let l) = lhs, case .rebooted(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.moduleConfig, .moduleConfig): return { + guard case .moduleConfig(let l) = lhs, case .moduleConfig(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.channel, .channel): return { + guard case .channel(let l) = lhs, case .channel(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.queueStatus, .queueStatus): return { + guard case .queueStatus(let l) = lhs, case .queueStatus(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.xmodemPacket, .xmodemPacket): return { + guard case .xmodemPacket(let l) = lhs, case .xmodemPacket(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.metadata, .metadata): return { + guard case .metadata(let l) = lhs, case .metadata(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.mqttClientProxyMessage, .mqttClientProxyMessage): return { + guard case .mqttClientProxyMessage(let l) = lhs, case .mqttClientProxyMessage(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.fileInfo, .fileInfo): return { + guard case .fileInfo(let l) = lhs, case .fileInfo(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.clientNotification, .clientNotification): return { + guard case .clientNotification(let l) = lhs, case .clientNotification(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } public init() {} } +/// +/// A notification message from the device to the client +/// To be used for important messages that should to be displayed to the user +/// in the form of push notifications or validation messages when saving +/// invalid configuration. +public struct ClientNotification { + // 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. + + /// + /// The id of the packet we're notifying in response to + public var replyID: UInt32 { + get {return _replyID ?? 0} + set {_replyID = newValue} + } + /// Returns true if `replyID` has been explicitly set. + public var hasReplyID: Bool {return self._replyID != nil} + /// Clears the value of `replyID`. Subsequent reads from it will return its default value. + public mutating func clearReplyID() {self._replyID = nil} + + /// + /// Seconds since 1970 - or 0 for unknown/unset + public var time: UInt32 = 0 + + /// + /// The level type of notification + public var level: LogRecord.Level = .unset + + /// + /// The message body of the notification + public var message: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _replyID: UInt32? = nil +} + /// /// Individual File info for the device -public struct FileInfo: Sendable { +public struct FileInfo { // 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. @@ -2317,7 +2676,7 @@ public struct FileInfo: Sendable { /// /// Packets/commands to the radio will be written (reliably) to the toRadio characteristic. /// Once the write completes the phone can assume it is handled. -public struct ToRadio: Sendable { +public struct ToRadio { // 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. @@ -2397,7 +2756,7 @@ public struct ToRadio: Sendable { /// /// Log levels, chosen to match python logging conventions. - public enum OneOf_PayloadVariant: Equatable, Sendable { + public enum OneOf_PayloadVariant: Equatable { /// /// Send this packet on the mesh case packet(MeshPacket) @@ -2424,6 +2783,40 @@ public struct ToRadio: Sendable { /// Heartbeat message (used to keep the device connection awake on serial) case heartbeat(Heartbeat) + #if !swift(>=4.1) + public static func ==(lhs: ToRadio.OneOf_PayloadVariant, rhs: ToRadio.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.packet, .packet): return { + guard case .packet(let l) = lhs, case .packet(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.wantConfigID, .wantConfigID): return { + guard case .wantConfigID(let l) = lhs, case .wantConfigID(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.disconnect, .disconnect): return { + guard case .disconnect(let l) = lhs, case .disconnect(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.xmodemPacket, .xmodemPacket): return { + guard case .xmodemPacket(let l) = lhs, case .xmodemPacket(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.mqttClientProxyMessage, .mqttClientProxyMessage): return { + guard case .mqttClientProxyMessage(let l) = lhs, case .mqttClientProxyMessage(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.heartbeat, .heartbeat): return { + guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } public init() {} @@ -2431,7 +2824,7 @@ public struct ToRadio: Sendable { /// /// Compressed message payload -public struct Compressed: @unchecked Sendable { +public struct Compressed { // 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. @@ -2451,7 +2844,7 @@ public struct Compressed: @unchecked Sendable { /// /// Full info on edges for a single node -public struct NeighborInfo: Sendable { +public struct NeighborInfo { // 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. @@ -2479,7 +2872,7 @@ public struct NeighborInfo: Sendable { /// /// A single edge in the mesh -public struct Neighbor: Sendable { +public struct Neighbor { // 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. @@ -2509,7 +2902,7 @@ public struct Neighbor: Sendable { /// /// Device metadata response -public struct DeviceMetadata: Sendable { +public struct DeviceMetadata { // 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. @@ -2562,7 +2955,7 @@ public struct DeviceMetadata: Sendable { /// /// A heartbeat message is sent to the node from the client to keep the connection alive. /// This is currently only needed to keep serial connections alive, but can be used by any PhoneAPI. -public struct Heartbeat: Sendable { +public struct Heartbeat { // 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. @@ -2574,7 +2967,7 @@ public struct Heartbeat: Sendable { /// /// RemoteHardwarePins associated with a node -public struct NodeRemoteHardwarePin: Sendable { +public struct NodeRemoteHardwarePin { // 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. @@ -2601,7 +2994,7 @@ public struct NodeRemoteHardwarePin: Sendable { fileprivate var _pin: RemoteHardwarePin? = nil } -public struct ChunkedPayload: @unchecked Sendable { +public struct ChunkedPayload { // 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. @@ -2629,7 +3022,7 @@ public struct ChunkedPayload: @unchecked Sendable { /// /// Wrapper message for broken repeated oneof support -public struct resend_chunks: Sendable { +public struct resend_chunks { // 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. @@ -2643,7 +3036,7 @@ public struct resend_chunks: Sendable { /// /// Responses to a ChunkedPayload request -public struct ChunkedPayloadResponse: Sendable { +public struct ChunkedPayloadResponse { // 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. @@ -2686,7 +3079,7 @@ public struct ChunkedPayloadResponse: Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_PayloadVariant: Equatable, Sendable { + public enum OneOf_PayloadVariant: Equatable { /// /// Request to transfer chunked payload case requestTransfer(Bool) @@ -2697,11 +3090,76 @@ public struct ChunkedPayloadResponse: Sendable { /// Request missing indexes in the chunked payload case resendChunks(resend_chunks) + #if !swift(>=4.1) + public static func ==(lhs: ChunkedPayloadResponse.OneOf_PayloadVariant, rhs: ChunkedPayloadResponse.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.requestTransfer, .requestTransfer): return { + guard case .requestTransfer(let l) = lhs, case .requestTransfer(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.acceptTransfer, .acceptTransfer): return { + guard case .acceptTransfer(let l) = lhs, case .acceptTransfer(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.resendChunks, .resendChunks): return { + guard case .resendChunks(let l) = lhs, case .resendChunks(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension HardwareModel: @unchecked Sendable {} +extension Constants: @unchecked Sendable {} +extension CriticalErrorCode: @unchecked Sendable {} +extension Position: @unchecked Sendable {} +extension Position.LocSource: @unchecked Sendable {} +extension Position.AltSource: @unchecked Sendable {} +extension User: @unchecked Sendable {} +extension RouteDiscovery: @unchecked Sendable {} +extension Routing: @unchecked Sendable {} +extension Routing.OneOf_Variant: @unchecked Sendable {} +extension Routing.Error: @unchecked Sendable {} +extension DataMessage: @unchecked Sendable {} +extension Waypoint: @unchecked Sendable {} +extension MqttClientProxyMessage: @unchecked Sendable {} +extension MqttClientProxyMessage.OneOf_PayloadVariant: @unchecked Sendable {} +extension MeshPacket: @unchecked Sendable {} +extension MeshPacket.OneOf_PayloadVariant: @unchecked Sendable {} +extension MeshPacket.Priority: @unchecked Sendable {} +extension MeshPacket.Delayed: @unchecked Sendable {} +extension NodeInfo: @unchecked Sendable {} +extension MyNodeInfo: @unchecked Sendable {} +extension LogRecord: @unchecked Sendable {} +extension LogRecord.Level: @unchecked Sendable {} +extension QueueStatus: @unchecked Sendable {} +extension FromRadio: @unchecked Sendable {} +extension FromRadio.OneOf_PayloadVariant: @unchecked Sendable {} +extension ClientNotification: @unchecked Sendable {} +extension FileInfo: @unchecked Sendable {} +extension ToRadio: @unchecked Sendable {} +extension ToRadio.OneOf_PayloadVariant: @unchecked Sendable {} +extension Compressed: @unchecked Sendable {} +extension NeighborInfo: @unchecked Sendable {} +extension Neighbor: @unchecked Sendable {} +extension DeviceMetadata: @unchecked Sendable {} +extension Heartbeat: @unchecked Sendable {} +extension NodeRemoteHardwarePin: @unchecked Sendable {} +extension ChunkedPayload: @unchecked Sendable {} +extension resend_chunks: @unchecked Sendable {} +extension ChunkedPayloadResponse: @unchecked Sendable {} +extension ChunkedPayloadResponse.OneOf_PayloadVariant: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -2779,6 +3237,10 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 69: .same(proto: "HELTEC_MESH_NODE_T114"), 70: .same(proto: "SENSECAP_INDICATOR"), 71: .same(proto: "TRACKER_T1000_E"), + 72: .same(proto: "RAK3172"), + 73: .same(proto: "WIO_E5"), + 74: .same(proto: "RADIOMASTER_900_BANDIT"), + 75: .same(proto: "ME25LS01_4Y10TD"), 255: .same(proto: "PRIVATE_HW"), ] } @@ -2804,6 +3266,8 @@ extension CriticalErrorCode: SwiftProtobuf._ProtoNameProviding { 9: .same(proto: "BROWNOUT"), 10: .same(proto: "SX1262_FAILURE"), 11: .same(proto: "RADIO_SPI_BUG"), + 12: .same(proto: "FLASH_CORRUPTION_RECOVERABLE"), + 13: .same(proto: "FLASH_CORRUPTION_UNRECOVERABLE"), ] } @@ -2836,22 +3300,22 @@ extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB ] fileprivate class _StorageClass { - var _latitudeI: Int32 = 0 - var _longitudeI: Int32 = 0 - var _altitude: Int32 = 0 + var _latitudeI: Int32? = nil + var _longitudeI: Int32? = nil + var _altitude: Int32? = nil var _time: UInt32 = 0 var _locationSource: Position.LocSource = .locUnset var _altitudeSource: Position.AltSource = .altUnset var _timestamp: UInt32 = 0 var _timestampMillisAdjust: Int32 = 0 - var _altitudeHae: Int32 = 0 - var _altitudeGeoidalSeparation: Int32 = 0 + var _altitudeHae: Int32? = nil + var _altitudeGeoidalSeparation: Int32? = nil var _pdop: UInt32 = 0 var _hdop: UInt32 = 0 var _vdop: UInt32 = 0 var _gpsAccuracy: UInt32 = 0 - var _groundSpeed: UInt32 = 0 - var _groundTrack: UInt32 = 0 + var _groundSpeed: UInt32? = nil + var _groundTrack: UInt32? = nil var _fixQuality: UInt32 = 0 var _fixType: UInt32 = 0 var _satsInView: UInt32 = 0 @@ -2945,15 +3409,19 @@ extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB public func traverse(visitor: inout V) throws { try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - if _storage._latitudeI != 0 { - try visitor.visitSingularSFixed32Field(value: _storage._latitudeI, fieldNumber: 1) - } - if _storage._longitudeI != 0 { - try visitor.visitSingularSFixed32Field(value: _storage._longitudeI, fieldNumber: 2) - } - if _storage._altitude != 0 { - try visitor.visitSingularInt32Field(value: _storage._altitude, fieldNumber: 3) - } + // 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._latitudeI { + try visitor.visitSingularSFixed32Field(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._longitudeI { + try visitor.visitSingularSFixed32Field(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._altitude { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 3) + } }() if _storage._time != 0 { try visitor.visitSingularFixed32Field(value: _storage._time, fieldNumber: 4) } @@ -2969,12 +3437,12 @@ extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if _storage._timestampMillisAdjust != 0 { try visitor.visitSingularInt32Field(value: _storage._timestampMillisAdjust, fieldNumber: 8) } - if _storage._altitudeHae != 0 { - try visitor.visitSingularSInt32Field(value: _storage._altitudeHae, fieldNumber: 9) - } - if _storage._altitudeGeoidalSeparation != 0 { - try visitor.visitSingularSInt32Field(value: _storage._altitudeGeoidalSeparation, fieldNumber: 10) - } + try { if let v = _storage._altitudeHae { + try visitor.visitSingularSInt32Field(value: v, fieldNumber: 9) + } }() + try { if let v = _storage._altitudeGeoidalSeparation { + try visitor.visitSingularSInt32Field(value: v, fieldNumber: 10) + } }() if _storage._pdop != 0 { try visitor.visitSingularUInt32Field(value: _storage._pdop, fieldNumber: 11) } @@ -2987,12 +3455,12 @@ extension Position: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if _storage._gpsAccuracy != 0 { try visitor.visitSingularUInt32Field(value: _storage._gpsAccuracy, fieldNumber: 14) } - if _storage._groundSpeed != 0 { - try visitor.visitSingularUInt32Field(value: _storage._groundSpeed, fieldNumber: 15) - } - if _storage._groundTrack != 0 { - try visitor.visitSingularUInt32Field(value: _storage._groundTrack, fieldNumber: 16) - } + try { if let v = _storage._groundSpeed { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 15) + } }() + try { if let v = _storage._groundTrack { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 16) + } }() if _storage._fixQuality != 0 { try visitor.visitSingularUInt32Field(value: _storage._fixQuality, fieldNumber: 17) } @@ -3084,6 +3552,7 @@ extension User: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, 5: .standard(proto: "hw_model"), 6: .standard(proto: "is_licensed"), 7: .same(proto: "role"), + 8: .standard(proto: "public_key"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -3099,6 +3568,7 @@ extension User: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, case 5: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }() case 6: try { try decoder.decodeSingularBoolField(value: &self.isLicensed) }() case 7: try { try decoder.decodeSingularEnumField(value: &self.role) }() + case 8: try { try decoder.decodeSingularBytesField(value: &self.publicKey) }() default: break } } @@ -3126,6 +3596,9 @@ extension User: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, if self.role != .client { try visitor.visitSingularEnumField(value: self.role, fieldNumber: 7) } + if !self.publicKey.isEmpty { + try visitor.visitSingularBytesField(value: self.publicKey, fieldNumber: 8) + } try unknownFields.traverse(visitor: &visitor) } @@ -3137,6 +3610,7 @@ extension User: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, if lhs.hwModel != rhs.hwModel {return false} if lhs.isLicensed != rhs.isLicensed {return false} if lhs.role != rhs.role {return false} + if lhs.publicKey != rhs.publicKey {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -3146,6 +3620,9 @@ extension RouteDiscovery: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement public static let protoMessageName: String = _protobuf_package + ".RouteDiscovery" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "route"), + 2: .standard(proto: "snr_towards"), + 3: .standard(proto: "route_back"), + 4: .standard(proto: "snr_back"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -3155,6 +3632,9 @@ extension RouteDiscovery: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeRepeatedFixed32Field(value: &self.route) }() + case 2: try { try decoder.decodeRepeatedInt32Field(value: &self.snrTowards) }() + case 3: try { try decoder.decodeRepeatedFixed32Field(value: &self.routeBack) }() + case 4: try { try decoder.decodeRepeatedInt32Field(value: &self.snrBack) }() default: break } } @@ -3164,11 +3644,23 @@ extension RouteDiscovery: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if !self.route.isEmpty { try visitor.visitPackedFixed32Field(value: self.route, fieldNumber: 1) } + if !self.snrTowards.isEmpty { + try visitor.visitPackedInt32Field(value: self.snrTowards, fieldNumber: 2) + } + if !self.routeBack.isEmpty { + try visitor.visitPackedFixed32Field(value: self.routeBack, fieldNumber: 3) + } + if !self.snrBack.isEmpty { + try visitor.visitPackedInt32Field(value: self.snrBack, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: RouteDiscovery, rhs: RouteDiscovery) -> Bool { if lhs.route != rhs.route {return false} + if lhs.snrTowards != rhs.snrTowards {return false} + if lhs.routeBack != rhs.routeBack {return false} + if lhs.snrBack != rhs.snrBack {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -3271,6 +3763,8 @@ extension Routing.Error: SwiftProtobuf._ProtoNameProviding { 9: .same(proto: "DUTY_CYCLE_LIMIT"), 32: .same(proto: "BAD_REQUEST"), 33: .same(proto: "NOT_AUTHORIZED"), + 34: .same(proto: "PKI_FAILED"), + 35: .same(proto: "PKI_UNKNOWN_PUBKEY"), ] } @@ -3368,8 +3862,8 @@ extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularUInt32Field(value: &self.id) }() - case 2: try { try decoder.decodeSingularSFixed32Field(value: &self.latitudeI) }() - case 3: try { try decoder.decodeSingularSFixed32Field(value: &self.longitudeI) }() + case 2: try { try decoder.decodeSingularSFixed32Field(value: &self._latitudeI) }() + case 3: try { try decoder.decodeSingularSFixed32Field(value: &self._longitudeI) }() case 4: try { try decoder.decodeSingularUInt32Field(value: &self.expire) }() case 5: try { try decoder.decodeSingularUInt32Field(value: &self.lockedTo) }() case 6: try { try decoder.decodeSingularStringField(value: &self.name) }() @@ -3381,15 +3875,19 @@ extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if self.id != 0 { try visitor.visitSingularUInt32Field(value: self.id, fieldNumber: 1) } - if self.latitudeI != 0 { - try visitor.visitSingularSFixed32Field(value: self.latitudeI, fieldNumber: 2) - } - if self.longitudeI != 0 { - try visitor.visitSingularSFixed32Field(value: self.longitudeI, fieldNumber: 3) - } + try { if let v = self._latitudeI { + try visitor.visitSingularSFixed32Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._longitudeI { + try visitor.visitSingularSFixed32Field(value: v, fieldNumber: 3) + } }() if self.expire != 0 { try visitor.visitSingularUInt32Field(value: self.expire, fieldNumber: 4) } @@ -3410,8 +3908,8 @@ extension Waypoint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB public static func ==(lhs: Waypoint, rhs: Waypoint) -> Bool { if lhs.id != rhs.id {return false} - if lhs.latitudeI != rhs.latitudeI {return false} - if lhs.longitudeI != rhs.longitudeI {return false} + if lhs._latitudeI != rhs._latitudeI {return false} + if lhs._longitudeI != rhs._longitudeI {return false} if lhs.expire != rhs.expire {return false} if lhs.lockedTo != rhs.lockedTo {return false} if lhs.name != rhs.name {return false} @@ -3512,6 +4010,8 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio 13: .same(proto: "delayed"), 14: .standard(proto: "via_mqtt"), 15: .standard(proto: "hop_start"), + 16: .standard(proto: "public_key"), + 17: .standard(proto: "pki_encrypted"), ] fileprivate class _StorageClass { @@ -3529,6 +4029,8 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio var _delayed: MeshPacket.Delayed = .noDelay var _viaMqtt: Bool = false var _hopStart: UInt32 = 0 + var _publicKey: Data = Data() + var _pkiEncrypted: Bool = false #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -3557,6 +4059,8 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio _delayed = source._delayed _viaMqtt = source._viaMqtt _hopStart = source._hopStart + _publicKey = source._publicKey + _pkiEncrypted = source._pkiEncrypted } } @@ -3609,6 +4113,8 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio case 13: try { try decoder.decodeSingularEnumField(value: &_storage._delayed) }() case 14: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }() case 15: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopStart) }() + case 16: try { try decoder.decodeSingularBytesField(value: &_storage._publicKey) }() + case 17: try { try decoder.decodeSingularBoolField(value: &_storage._pkiEncrypted) }() default: break } } @@ -3647,7 +4153,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._rxTime != 0 { try visitor.visitSingularFixed32Field(value: _storage._rxTime, fieldNumber: 7) } - if _storage._rxSnr.bitPattern != 0 { + if _storage._rxSnr != 0 { try visitor.visitSingularFloatField(value: _storage._rxSnr, fieldNumber: 8) } if _storage._hopLimit != 0 { @@ -3671,6 +4177,12 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._hopStart != 0 { try visitor.visitSingularUInt32Field(value: _storage._hopStart, fieldNumber: 15) } + if !_storage._publicKey.isEmpty { + try visitor.visitSingularBytesField(value: _storage._publicKey, fieldNumber: 16) + } + if _storage._pkiEncrypted != false { + try visitor.visitSingularBoolField(value: _storage._pkiEncrypted, fieldNumber: 17) + } } try unknownFields.traverse(visitor: &visitor) } @@ -3694,6 +4206,8 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._delayed != rhs_storage._delayed {return false} if _storage._viaMqtt != rhs_storage._viaMqtt {return false} if _storage._hopStart != rhs_storage._hopStart {return false} + if _storage._publicKey != rhs_storage._publicKey {return false} + if _storage._pkiEncrypted != rhs_storage._pkiEncrypted {return false} return true } if !storagesAreEqual {return false} @@ -3822,7 +4336,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB try { if let v = _storage._position { try visitor.visitSingularMessageField(value: v, fieldNumber: 3) } }() - if _storage._snr.bitPattern != 0 { + if _storage._snr != 0 { try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) } if _storage._lastHeard != 0 { @@ -4045,6 +4559,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 13: .same(proto: "metadata"), 14: .same(proto: "mqttClientProxyMessage"), 15: .same(proto: "fileInfo"), + 16: .same(proto: "clientNotification"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -4226,6 +4741,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation self.payloadVariant = .fileInfo(v) } }() + case 16: try { + var v: ClientNotification? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .clientNotification(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .clientNotification(v) + } + }() default: break } } @@ -4296,6 +4824,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation guard case .fileInfo(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 15) }() + case .clientNotification?: try { + guard case .clientNotification(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 16) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -4309,6 +4841,60 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation } } +extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".ClientNotification" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "reply_id"), + 2: .same(proto: "time"), + 3: .same(proto: "level"), + 4: .same(proto: "message"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._replyID) }() + case 2: try { try decoder.decodeSingularFixed32Field(value: &self.time) }() + case 3: try { try decoder.decodeSingularEnumField(value: &self.level) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.message) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._replyID { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + if self.time != 0 { + try visitor.visitSingularFixed32Field(value: self.time, fieldNumber: 2) + } + if self.level != .unset { + try visitor.visitSingularEnumField(value: self.level, fieldNumber: 3) + } + if !self.message.isEmpty { + try visitor.visitSingularStringField(value: self.message, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: ClientNotification, rhs: ClientNotification) -> Bool { + if lhs._replyID != rhs._replyID {return false} + if lhs.time != rhs.time {return false} + if lhs.level != rhs.level {return false} + if lhs.message != rhs.message {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension FileInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".FileInfo" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -4595,7 +5181,7 @@ extension Neighbor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if self.nodeID != 0 { try visitor.visitSingularUInt32Field(value: self.nodeID, fieldNumber: 1) } - if self.snr.bitPattern != 0 { + if self.snr != 0 { try visitor.visitSingularFloatField(value: self.snr, fieldNumber: 2) } if self.lastRxTime != 0 { @@ -4708,8 +5294,8 @@ extension Heartbeat: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation public static let _protobuf_nameMap = SwiftProtobuf._NameMap() public mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} + while let _ = try decoder.nextFieldNumber() { + } } public func traverse(visitor: inout V) throws { diff --git a/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift index 6f3b2d76..3186c349 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/module_config.pb.swift @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public enum RemoteHardwarePinType: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum RemoteHardwarePinType: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -58,18 +58,24 @@ public enum RemoteHardwarePinType: SwiftProtobuf.Enum, Swift.CaseIterable { } } +} + +#if swift(>=4.2) + +extension RemoteHardwarePinType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [RemoteHardwarePinType] = [ .unknown, .digitalRead, .digitalWrite, ] - } +#endif // swift(>=4.2) + /// /// Module Config -public struct ModuleConfig: Sendable { +public struct ModuleConfig { // 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. @@ -212,7 +218,7 @@ public struct ModuleConfig: Sendable { /// /// TODO: REPLACE - public enum OneOf_PayloadVariant: Equatable, Sendable { + public enum OneOf_PayloadVariant: Equatable { /// /// TODO: REPLACE case mqtt(ModuleConfig.MQTTConfig) @@ -253,11 +259,73 @@ public struct ModuleConfig: Sendable { /// TODO: REPLACE case paxcounter(ModuleConfig.PaxcounterConfig) + #if !swift(>=4.1) + public static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.OneOf_PayloadVariant) -> Bool { + // 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 (lhs, rhs) { + case (.mqtt, .mqtt): return { + guard case .mqtt(let l) = lhs, case .mqtt(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.serial, .serial): return { + guard case .serial(let l) = lhs, case .serial(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.externalNotification, .externalNotification): return { + guard case .externalNotification(let l) = lhs, case .externalNotification(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.storeForward, .storeForward): return { + guard case .storeForward(let l) = lhs, case .storeForward(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.rangeTest, .rangeTest): return { + guard case .rangeTest(let l) = lhs, case .rangeTest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.telemetry, .telemetry): return { + guard case .telemetry(let l) = lhs, case .telemetry(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.cannedMessage, .cannedMessage): return { + guard case .cannedMessage(let l) = lhs, case .cannedMessage(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.audio, .audio): return { + guard case .audio(let l) = lhs, case .audio(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.remoteHardware, .remoteHardware): return { + guard case .remoteHardware(let l) = lhs, case .remoteHardware(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.neighborInfo, .neighborInfo): return { + guard case .neighborInfo(let l) = lhs, case .neighborInfo(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.ambientLighting, .ambientLighting): return { + guard case .ambientLighting(let l) = lhs, case .ambientLighting(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.detectionSensor, .detectionSensor): return { + guard case .detectionSensor(let l) = lhs, case .detectionSensor(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.paxcounter, .paxcounter): return { + guard case .paxcounter(let l) = lhs, case .paxcounter(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } /// /// MQTT Client Config - public struct MQTTConfig: Sendable { + public struct MQTTConfig { // 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. @@ -332,7 +400,7 @@ public struct ModuleConfig: Sendable { /// /// Settings for reporting unencrypted information about our node to a map via MQTT - public struct MapReportSettings: Sendable { + public struct MapReportSettings { // 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. @@ -352,7 +420,7 @@ public struct ModuleConfig: Sendable { /// /// RemoteHardwareModule Config - public struct RemoteHardwareConfig: Sendable { + public struct RemoteHardwareConfig { // 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. @@ -376,7 +444,7 @@ public struct ModuleConfig: Sendable { /// /// NeighborInfoModule Config - public struct NeighborInfoConfig: Sendable { + public struct NeighborInfoConfig { // 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. @@ -397,7 +465,7 @@ public struct ModuleConfig: Sendable { /// /// Detection Sensor Module Config - public struct DetectionSensorConfig: Sendable { + public struct DetectionSensorConfig { // 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. @@ -448,7 +516,7 @@ public struct ModuleConfig: Sendable { /// /// Audio Config for codec2 voice - public struct AudioConfig: Sendable { + public struct AudioConfig { // 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. @@ -485,7 +553,7 @@ public struct ModuleConfig: Sendable { /// /// Baudrate for codec2 voice - public enum Audio_Baud: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Audio_Baud: SwiftProtobuf.Enum { public typealias RawValue = Int case codec2Default // = 0 case codec23200 // = 1 @@ -532,19 +600,6 @@ public struct ModuleConfig: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [ - .codec2Default, - .codec23200, - .codec22400, - .codec21600, - .codec21400, - .codec21300, - .codec21200, - .codec2700, - .codec2700B, - ] - } public init() {} @@ -552,7 +607,7 @@ public struct ModuleConfig: Sendable { /// /// Config for the Paxcounter Module - public struct PaxcounterConfig: Sendable { + public struct PaxcounterConfig { // 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. @@ -578,7 +633,7 @@ public struct ModuleConfig: Sendable { /// /// Serial Config - public struct SerialConfig: Sendable { + public struct SerialConfig { // 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. @@ -621,7 +676,7 @@ public struct ModuleConfig: Sendable { /// /// TODO: REPLACE - public enum Serial_Baud: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Serial_Baud: SwiftProtobuf.Enum { public typealias RawValue = Int case baudDefault // = 0 case baud110 // = 1 @@ -689,31 +744,11 @@ public struct ModuleConfig: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [ - .baudDefault, - .baud110, - .baud300, - .baud600, - .baud1200, - .baud2400, - .baud4800, - .baud9600, - .baud19200, - .baud38400, - .baud57600, - .baud115200, - .baud230400, - .baud460800, - .baud576000, - .baud921600, - ] - } /// /// TODO: REPLACE - public enum Serial_Mode: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Serial_Mode: SwiftProtobuf.Enum { public typealias RawValue = Int case `default` // = 0 case simple // = 1 @@ -758,17 +793,6 @@ public struct ModuleConfig: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [ - .default, - .simple, - .proto, - .textmsg, - .nmea, - .caltopo, - .ws85, - ] - } public init() {} @@ -776,7 +800,7 @@ public struct ModuleConfig: Sendable { /// /// External Notifications Config - public struct ExternalNotificationConfig: Sendable { + public struct ExternalNotificationConfig { // 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. @@ -859,7 +883,7 @@ public struct ModuleConfig: Sendable { /// /// Store and Forward Module Config - public struct StoreForwardConfig: Sendable { + public struct StoreForwardConfig { // 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. @@ -895,7 +919,7 @@ public struct ModuleConfig: Sendable { /// /// Preferences for the RangeTestModule - public struct RangeTestConfig: Sendable { + public struct RangeTestConfig { // 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. @@ -920,7 +944,7 @@ public struct ModuleConfig: Sendable { /// /// Configuration for both device and environment metrics - public struct TelemetryConfig: Sendable { + public struct TelemetryConfig { // 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. @@ -977,7 +1001,7 @@ public struct ModuleConfig: Sendable { /// /// TODO: REPLACE - public struct CannedMessageConfig: Sendable { + public struct CannedMessageConfig { // 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. @@ -1020,7 +1044,7 @@ public struct ModuleConfig: Sendable { /// /// Input event origin accepted by the canned message module. - /// Can be e.g. "rotEnc1", "upDownEnc1" or keyword "_any" + /// Can be e.g. "rotEnc1", "upDownEnc1", "scanAndSelect", "cardkb", "serialkb", or keyword "_any" public var allowInputSource: String = String() /// @@ -1032,7 +1056,7 @@ public struct ModuleConfig: Sendable { /// /// TODO: REPLACE - public enum InputEventChar: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum InputEventChar: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -1100,18 +1124,6 @@ public struct ModuleConfig: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [ - .none, - .up, - .down, - .left, - .right, - .select, - .back, - .cancel, - ] - } public init() {} @@ -1120,7 +1132,7 @@ public struct ModuleConfig: Sendable { /// ///Ambient Lighting Module - Settings for control of onboard LEDs to allow users to adjust the brightness levels and respective color levels. ///Initially created for the RAK14001 RGB LED module. - public struct AmbientLightingConfig: Sendable { + public struct AmbientLightingConfig { // 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. @@ -1153,9 +1165,77 @@ public struct ModuleConfig: Sendable { public init() {} } +#if swift(>=4.2) + +extension ModuleConfig.AudioConfig.Audio_Baud: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.AudioConfig.Audio_Baud] = [ + .codec2Default, + .codec23200, + .codec22400, + .codec21600, + .codec21400, + .codec21300, + .codec21200, + .codec2700, + .codec2700B, + ] +} + +extension ModuleConfig.SerialConfig.Serial_Baud: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.SerialConfig.Serial_Baud] = [ + .baudDefault, + .baud110, + .baud300, + .baud600, + .baud1200, + .baud2400, + .baud4800, + .baud9600, + .baud19200, + .baud38400, + .baud57600, + .baud115200, + .baud230400, + .baud460800, + .baud576000, + .baud921600, + ] +} + +extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.SerialConfig.Serial_Mode] = [ + .default, + .simple, + .proto, + .textmsg, + .nmea, + .caltopo, + .ws85, + ] +} + +extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [ModuleConfig.CannedMessageConfig.InputEventChar] = [ + .none, + .up, + .down, + .left, + .right, + .select, + .back, + .cancel, + ] +} + +#endif // swift(>=4.2) + /// /// A GPIO pin definition for remote hardware module -public struct RemoteHardwarePin: Sendable { +public struct RemoteHardwarePin { // 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. @@ -1177,6 +1257,31 @@ public struct RemoteHardwarePin: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension RemoteHardwarePinType: @unchecked Sendable {} +extension ModuleConfig: @unchecked Sendable {} +extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {} +extension ModuleConfig.MQTTConfig: @unchecked Sendable {} +extension ModuleConfig.MapReportSettings: @unchecked Sendable {} +extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {} +extension ModuleConfig.NeighborInfoConfig: @unchecked Sendable {} +extension ModuleConfig.DetectionSensorConfig: @unchecked Sendable {} +extension ModuleConfig.AudioConfig: @unchecked Sendable {} +extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {} +extension ModuleConfig.PaxcounterConfig: @unchecked Sendable {} +extension ModuleConfig.SerialConfig: @unchecked Sendable {} +extension ModuleConfig.SerialConfig.Serial_Baud: @unchecked Sendable {} +extension ModuleConfig.SerialConfig.Serial_Mode: @unchecked Sendable {} +extension ModuleConfig.ExternalNotificationConfig: @unchecked Sendable {} +extension ModuleConfig.StoreForwardConfig: @unchecked Sendable {} +extension ModuleConfig.RangeTestConfig: @unchecked Sendable {} +extension ModuleConfig.TelemetryConfig: @unchecked Sendable {} +extension ModuleConfig.CannedMessageConfig: @unchecked Sendable {} +extension ModuleConfig.CannedMessageConfig.InputEventChar: @unchecked Sendable {} +extension ModuleConfig.AmbientLightingConfig: @unchecked Sendable {} +extension RemoteHardwarePin: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift index fc5e37a1..efe6cdd5 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mqtt.pb.swift @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// This message wraps a MeshPacket with extra metadata about the sender and how it arrived. -public struct ServiceEnvelope: Sendable { +public struct ServiceEnvelope { // 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. @@ -57,7 +57,7 @@ public struct ServiceEnvelope: Sendable { /// /// Information about a node intended to be reported unencrypted to a map using MQTT. -public struct MapReport: Sendable { +public struct MapReport { // 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. @@ -121,6 +121,11 @@ public struct MapReport: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension ServiceEnvelope: @unchecked Sendable {} +extension MapReport: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift index f82b3c51..cf8aa463 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/paxcount.pb.swift @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// TODO: REPLACE -public struct Paxcount: Sendable { +public struct Paxcount { // 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. @@ -44,6 +44,10 @@ public struct Paxcount: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension Paxcount: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift index c5348a8a..dd7e036f 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/portnums.pb.swift @@ -33,7 +33,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// Note: This was formerly a Type enum named 'typ' with the same id # /// We have change to this 'portnum' based scheme for specifying app handlers for particular payloads. /// This change is backwards compatible by treating the legacy OPAQUE/CLEAR_TEXT values identically. -public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum PortNum: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -167,7 +167,7 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { /// /// Provides a traceroute functionality to show the route a packet towards - /// a certain destination would take on the mesh. + /// a certain destination would take on the mesh. Contains a RouteDiscovery message as payload. /// ENCODING: Protobuf case tracerouteApp // = 70 @@ -277,6 +277,11 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { } } +} + +#if swift(>=4.2) + +extension PortNum: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [PortNum] = [ .unknownApp, @@ -308,9 +313,14 @@ public enum PortNum: SwiftProtobuf.Enum, Swift.CaseIterable { .atakForwarder, .max, ] - } +#endif // swift(>=4.2) + +#if swift(>=5.5) && canImport(_Concurrency) +extension PortNum: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. extension PortNum: SwiftProtobuf._ProtoNameProviding { diff --git a/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift index 9c61e6d0..5f51e948 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/powermon.pb.swift @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs). ///But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us) -public struct PowerMon: Sendable { +public struct PowerMon { // 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. @@ -31,7 +31,7 @@ public struct PowerMon: Sendable { /// Any significant power changing event in meshtastic should be tagged with a powermon state transition. ///If you are making new meshtastic features feel free to add new entries at the end of this definition. - public enum State: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum State: SwiftProtobuf.Enum { public typealias RawValue = Int case none // = 0 case cpuDeepSleep // = 1 @@ -104,31 +104,37 @@ public struct PowerMon: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [PowerMon.State] = [ - .none, - .cpuDeepSleep, - .cpuLightSleep, - .vext1On, - .loraRxon, - .loraTxon, - .loraRxactive, - .btOn, - .ledOn, - .screenOn, - .screenDrawing, - .wifiOn, - .gpsActive, - ] - } public init() {} } +#if swift(>=4.2) + +extension PowerMon.State: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [PowerMon.State] = [ + .none, + .cpuDeepSleep, + .cpuLightSleep, + .vext1On, + .loraRxon, + .loraTxon, + .loraRxactive, + .btOn, + .ledOn, + .screenOn, + .screenDrawing, + .wifiOn, + .gpsActive, + ] +} + +#endif // swift(>=4.2) + /// /// PowerStress testing support via the C++ PowerStress module -public struct PowerStressMessage: Sendable { +public struct PowerStressMessage { // 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. @@ -145,7 +151,7 @@ public struct PowerStressMessage: Sendable { /// What operation would we like the UUT to perform. ///note: senders should probably set want_response in their request packets, so that they can know when the state ///machine has started processing their request - public enum Opcode: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Opcode: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -266,35 +272,48 @@ public struct PowerStressMessage: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [PowerStressMessage.Opcode] = [ - .unset, - .printInfo, - .forceQuiet, - .endQuiet, - .screenOn, - .screenOff, - .cpuIdle, - .cpuDeepsleep, - .cpuFullon, - .ledOn, - .ledOff, - .loraOff, - .loraTx, - .loraRx, - .btOff, - .btOn, - .wifiOff, - .wifiOn, - .gpsOff, - .gpsOn, - ] - } public init() {} } +#if swift(>=4.2) + +extension PowerStressMessage.Opcode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [PowerStressMessage.Opcode] = [ + .unset, + .printInfo, + .forceQuiet, + .endQuiet, + .screenOn, + .screenOff, + .cpuIdle, + .cpuDeepsleep, + .cpuFullon, + .ledOn, + .ledOff, + .loraOff, + .loraTx, + .loraRx, + .btOff, + .btOn, + .wifiOff, + .wifiOn, + .gpsOff, + .gpsOn, + ] +} + +#endif // swift(>=4.2) + +#if swift(>=5.5) && canImport(_Concurrency) +extension PowerMon: @unchecked Sendable {} +extension PowerMon.State: @unchecked Sendable {} +extension PowerStressMessage: @unchecked Sendable {} +extension PowerStressMessage.Opcode: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -304,8 +323,8 @@ extension PowerMon: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB public static let _protobuf_nameMap = SwiftProtobuf._NameMap() public mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} + while let _ = try decoder.nextFieldNumber() { + } } public func traverse(visitor: inout V) throws { @@ -360,7 +379,7 @@ extension PowerStressMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImple if self.cmd != .unset { try visitor.visitSingularEnumField(value: self.cmd, fieldNumber: 1) } - if self.numSeconds.bitPattern != 0 { + if self.numSeconds != 0 { try visitor.visitSingularFloatField(value: self.numSeconds, fieldNumber: 2) } try unknownFields.traverse(visitor: &visitor) diff --git a/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift index 60f64504..ac6eeb26 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/remote_hardware.pb.swift @@ -30,7 +30,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// because no security yet (beyond the channel mechanism). /// It should be off by default and then protected based on some TBD mechanism /// (a special channel once multichannel support is included?) -public struct HardwareMessage: Sendable { +public struct HardwareMessage { // 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. @@ -52,7 +52,7 @@ public struct HardwareMessage: Sendable { /// /// TODO: REPLACE - public enum TypeEnum: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum TypeEnum: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -110,21 +110,32 @@ public struct HardwareMessage: Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [HardwareMessage.TypeEnum] = [ - .unset, - .writeGpios, - .watchGpios, - .gpiosChanged, - .readGpios, - .readGpiosReply, - ] - } public init() {} } +#if swift(>=4.2) + +extension HardwareMessage.TypeEnum: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [HardwareMessage.TypeEnum] = [ + .unset, + .writeGpios, + .watchGpios, + .gpiosChanged, + .readGpios, + .readGpiosReply, + ] +} + +#endif // swift(>=4.2) + +#if swift(>=5.5) && canImport(_Concurrency) +extension HardwareMessage: @unchecked Sendable {} +extension HardwareMessage.TypeEnum: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift index c1f3f678..6fdf3208 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/rtttl.pb.swift @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// Canned message module configuration. -public struct RTTTLConfig: Sendable { +public struct RTTTLConfig { // 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. @@ -36,6 +36,10 @@ public struct RTTTLConfig: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension RTTTLConfig: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift index 0b67eaf6..54efa77b 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/storeforward.pb.swift @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// TODO: REPLACE -public struct StoreAndForward: @unchecked Sendable { +public struct StoreAndForward { // 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. @@ -79,7 +79,7 @@ public struct StoreAndForward: @unchecked Sendable { /// /// TODO: REPLACE - public enum OneOf_Variant: Equatable, @unchecked Sendable { + public enum OneOf_Variant: Equatable { /// /// TODO: REPLACE case stats(StoreAndForward.Statistics) @@ -93,12 +93,38 @@ public struct StoreAndForward: @unchecked Sendable { /// Text from history message. case text(Data) + #if !swift(>=4.1) + public static func ==(lhs: StoreAndForward.OneOf_Variant, rhs: StoreAndForward.OneOf_Variant) -> Bool { + // 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 (lhs, rhs) { + case (.stats, .stats): return { + guard case .stats(let l) = lhs, case .stats(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.history, .history): return { + guard case .history(let l) = lhs, case .history(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.heartbeat, .heartbeat): return { + guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.text, .text): return { + guard case .text(let l) = lhs, case .text(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } /// /// 001 - 063 = From Router /// 064 - 127 = From Client - public enum RequestResponse: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum RequestResponse: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -216,31 +242,11 @@ public struct StoreAndForward: @unchecked Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [StoreAndForward.RequestResponse] = [ - .unset, - .routerError, - .routerHeartbeat, - .routerPing, - .routerPong, - .routerBusy, - .routerHistory, - .routerStats, - .routerTextDirect, - .routerTextBroadcast, - .clientError, - .clientHistory, - .clientStats, - .clientPing, - .clientPong, - .clientAbort, - ] - } /// /// TODO: REPLACE - public struct Statistics: Sendable { + public struct Statistics { // 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. @@ -288,7 +294,7 @@ public struct StoreAndForward: @unchecked Sendable { /// /// TODO: REPLACE - public struct History: Sendable { + public struct History { // 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. @@ -313,7 +319,7 @@ public struct StoreAndForward: @unchecked Sendable { /// /// TODO: REPLACE - public struct Heartbeat: Sendable { + public struct Heartbeat { // 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. @@ -334,6 +340,41 @@ public struct StoreAndForward: @unchecked Sendable { public init() {} } +#if swift(>=4.2) + +extension StoreAndForward.RequestResponse: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [StoreAndForward.RequestResponse] = [ + .unset, + .routerError, + .routerHeartbeat, + .routerPing, + .routerPong, + .routerBusy, + .routerHistory, + .routerStats, + .routerTextDirect, + .routerTextBroadcast, + .clientError, + .clientHistory, + .clientStats, + .clientPing, + .clientPong, + .clientAbort, + ] +} + +#endif // swift(>=4.2) + +#if swift(>=5.5) && canImport(_Concurrency) +extension StoreAndForward: @unchecked Sendable {} +extension StoreAndForward.OneOf_Variant: @unchecked Sendable {} +extension StoreAndForward.RequestResponse: @unchecked Sendable {} +extension StoreAndForward.Statistics: @unchecked Sendable {} +extension StoreAndForward.History: @unchecked Sendable {} +extension StoreAndForward.Heartbeat: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift index 7a9b81de..e4b9ee08 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/telemetry.pb.swift @@ -22,7 +22,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP /// /// Supported I2C Sensors for telemetry in Meshtastic -public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { +public enum TelemetrySensorType: SwiftProtobuf.Enum { public typealias RawValue = Int /// @@ -128,6 +128,18 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { /// /// NAU7802 Scale Chip or compatible case nau7802 // = 25 + + /// + /// BMP3XX High accuracy temperature and pressure + case bmp3Xx // = 26 + + /// + /// ICM-20948 9-Axis digital motion processor + case icm20948 // = 27 + + /// + /// MAX17048 1S lipo battery sensor (voltage, state of charge, time to go) + case max17048 // = 28 case UNRECOGNIZED(Int) public init() { @@ -162,6 +174,9 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { case 23: self = .aht10 case 24: self = .dfrobotLark case 25: self = .nau7802 + case 26: self = .bmp3Xx + case 27: self = .icm20948 + case 28: self = .max17048 default: self = .UNRECOGNIZED(rawValue) } } @@ -194,10 +209,18 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { case .aht10: return 23 case .dfrobotLark: return 24 case .nau7802: return 25 + case .bmp3Xx: return 26 + case .icm20948: return 27 + case .max17048: return 28 case .UNRECOGNIZED(let i): return i } } +} + +#if swift(>=4.2) + +extension TelemetrySensorType: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. public static let allCases: [TelemetrySensorType] = [ .sensorUnset, @@ -226,45 +249,90 @@ public enum TelemetrySensorType: SwiftProtobuf.Enum, Swift.CaseIterable { .aht10, .dfrobotLark, .nau7802, + .bmp3Xx, + .icm20948, + .max17048, ] - } +#endif // swift(>=4.2) + /// /// Key native device metrics such as battery level -public struct DeviceMetrics: Sendable { +public struct DeviceMetrics { // 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. /// /// 0-100 (>100 means powered) - public var batteryLevel: UInt32 = 0 + public var batteryLevel: UInt32 { + get {return _batteryLevel ?? 0} + set {_batteryLevel = newValue} + } + /// Returns true if `batteryLevel` has been explicitly set. + public var hasBatteryLevel: Bool {return self._batteryLevel != nil} + /// Clears the value of `batteryLevel`. Subsequent reads from it will return its default value. + public mutating func clearBatteryLevel() {self._batteryLevel = nil} /// /// Voltage measured - public var voltage: Float = 0 + public var voltage: Float { + get {return _voltage ?? 0} + set {_voltage = newValue} + } + /// Returns true if `voltage` has been explicitly set. + public var hasVoltage: Bool {return self._voltage != nil} + /// Clears the value of `voltage`. Subsequent reads from it will return its default value. + public mutating func clearVoltage() {self._voltage = nil} /// /// Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise). - public var channelUtilization: Float = 0 + public var channelUtilization: Float { + get {return _channelUtilization ?? 0} + set {_channelUtilization = newValue} + } + /// Returns true if `channelUtilization` has been explicitly set. + public var hasChannelUtilization: Bool {return self._channelUtilization != nil} + /// Clears the value of `channelUtilization`. Subsequent reads from it will return its default value. + public mutating func clearChannelUtilization() {self._channelUtilization = nil} /// /// Percent of airtime for transmission used within the last hour. - public var airUtilTx: Float = 0 + public var airUtilTx: Float { + get {return _airUtilTx ?? 0} + set {_airUtilTx = newValue} + } + /// Returns true if `airUtilTx` has been explicitly set. + public var hasAirUtilTx: Bool {return self._airUtilTx != nil} + /// Clears the value of `airUtilTx`. Subsequent reads from it will return its default value. + public mutating func clearAirUtilTx() {self._airUtilTx = nil} /// /// How long the device has been running since the last reboot (in seconds) - public var uptimeSeconds: UInt32 = 0 + public var uptimeSeconds: UInt32 { + get {return _uptimeSeconds ?? 0} + set {_uptimeSeconds = newValue} + } + /// Returns true if `uptimeSeconds` has been explicitly set. + public var hasUptimeSeconds: Bool {return self._uptimeSeconds != nil} + /// Clears the value of `uptimeSeconds`. Subsequent reads from it will return its default value. + public mutating func clearUptimeSeconds() {self._uptimeSeconds = nil} public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _batteryLevel: UInt32? = nil + fileprivate var _voltage: Float? = nil + fileprivate var _channelUtilization: Float? = nil + fileprivate var _airUtilTx: Float? = nil + fileprivate var _uptimeSeconds: UInt32? = nil } /// /// Weather station or other environmental metrics -public struct EnvironmentMetrics: @unchecked Sendable { +public struct EnvironmentMetrics { // 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. @@ -272,123 +340,191 @@ public struct EnvironmentMetrics: @unchecked Sendable { /// /// Temperature measured public var temperature: Float { - get {return _storage._temperature} + get {return _storage._temperature ?? 0} set {_uniqueStorage()._temperature = newValue} } + /// Returns true if `temperature` has been explicitly set. + public var hasTemperature: Bool {return _storage._temperature != nil} + /// Clears the value of `temperature`. Subsequent reads from it will return its default value. + public mutating func clearTemperature() {_uniqueStorage()._temperature = nil} /// /// Relative humidity percent measured public var relativeHumidity: Float { - get {return _storage._relativeHumidity} + get {return _storage._relativeHumidity ?? 0} set {_uniqueStorage()._relativeHumidity = newValue} } + /// Returns true if `relativeHumidity` has been explicitly set. + public var hasRelativeHumidity: Bool {return _storage._relativeHumidity != nil} + /// Clears the value of `relativeHumidity`. Subsequent reads from it will return its default value. + public mutating func clearRelativeHumidity() {_uniqueStorage()._relativeHumidity = nil} /// /// Barometric pressure in hPA measured public var barometricPressure: Float { - get {return _storage._barometricPressure} + get {return _storage._barometricPressure ?? 0} set {_uniqueStorage()._barometricPressure = newValue} } + /// Returns true if `barometricPressure` has been explicitly set. + public var hasBarometricPressure: Bool {return _storage._barometricPressure != nil} + /// Clears the value of `barometricPressure`. Subsequent reads from it will return its default value. + public mutating func clearBarometricPressure() {_uniqueStorage()._barometricPressure = nil} /// /// Gas resistance in MOhm measured public var gasResistance: Float { - get {return _storage._gasResistance} + get {return _storage._gasResistance ?? 0} set {_uniqueStorage()._gasResistance = newValue} } + /// Returns true if `gasResistance` has been explicitly set. + public var hasGasResistance: Bool {return _storage._gasResistance != nil} + /// Clears the value of `gasResistance`. Subsequent reads from it will return its default value. + public mutating func clearGasResistance() {_uniqueStorage()._gasResistance = nil} /// /// Voltage measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) public var voltage: Float { - get {return _storage._voltage} + get {return _storage._voltage ?? 0} set {_uniqueStorage()._voltage = newValue} } + /// Returns true if `voltage` has been explicitly set. + public var hasVoltage: Bool {return _storage._voltage != nil} + /// Clears the value of `voltage`. Subsequent reads from it will return its default value. + public mutating func clearVoltage() {_uniqueStorage()._voltage = nil} /// /// Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) public var current: Float { - get {return _storage._current} + get {return _storage._current ?? 0} set {_uniqueStorage()._current = newValue} } + /// Returns true if `current` has been explicitly set. + public var hasCurrent: Bool {return _storage._current != nil} + /// Clears the value of `current`. Subsequent reads from it will return its default value. + public mutating func clearCurrent() {_uniqueStorage()._current = nil} /// /// relative scale IAQ value as measured by Bosch BME680 . value 0-500. /// Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. public var iaq: UInt32 { - get {return _storage._iaq} + get {return _storage._iaq ?? 0} set {_uniqueStorage()._iaq = newValue} } + /// Returns true if `iaq` has been explicitly set. + public var hasIaq: Bool {return _storage._iaq != nil} + /// Clears the value of `iaq`. Subsequent reads from it will return its default value. + public mutating func clearIaq() {_uniqueStorage()._iaq = nil} /// /// RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. public var distance: Float { - get {return _storage._distance} + get {return _storage._distance ?? 0} set {_uniqueStorage()._distance = newValue} } + /// Returns true if `distance` has been explicitly set. + public var hasDistance: Bool {return _storage._distance != nil} + /// Clears the value of `distance`. Subsequent reads from it will return its default value. + public mutating func clearDistance() {_uniqueStorage()._distance = nil} /// /// VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. public var lux: Float { - get {return _storage._lux} + get {return _storage._lux ?? 0} set {_uniqueStorage()._lux = newValue} } + /// Returns true if `lux` has been explicitly set. + public var hasLux: Bool {return _storage._lux != nil} + /// Clears the value of `lux`. Subsequent reads from it will return its default value. + public mutating func clearLux() {_uniqueStorage()._lux = nil} /// /// VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. public var whiteLux: Float { - get {return _storage._whiteLux} + get {return _storage._whiteLux ?? 0} set {_uniqueStorage()._whiteLux = newValue} } + /// Returns true if `whiteLux` has been explicitly set. + public var hasWhiteLux: Bool {return _storage._whiteLux != nil} + /// Clears the value of `whiteLux`. Subsequent reads from it will return its default value. + public mutating func clearWhiteLux() {_uniqueStorage()._whiteLux = nil} /// /// Infrared lux public var irLux: Float { - get {return _storage._irLux} + get {return _storage._irLux ?? 0} set {_uniqueStorage()._irLux = newValue} } + /// Returns true if `irLux` has been explicitly set. + public var hasIrLux: Bool {return _storage._irLux != nil} + /// Clears the value of `irLux`. Subsequent reads from it will return its default value. + public mutating func clearIrLux() {_uniqueStorage()._irLux = nil} /// /// Ultraviolet lux public var uvLux: Float { - get {return _storage._uvLux} + get {return _storage._uvLux ?? 0} set {_uniqueStorage()._uvLux = newValue} } + /// Returns true if `uvLux` has been explicitly set. + public var hasUvLux: Bool {return _storage._uvLux != nil} + /// Clears the value of `uvLux`. Subsequent reads from it will return its default value. + public mutating func clearUvLux() {_uniqueStorage()._uvLux = nil} /// /// Wind direction in degrees /// 0 degrees = North, 90 = East, etc... public var windDirection: UInt32 { - get {return _storage._windDirection} + get {return _storage._windDirection ?? 0} set {_uniqueStorage()._windDirection = newValue} } + /// Returns true if `windDirection` has been explicitly set. + public var hasWindDirection: Bool {return _storage._windDirection != nil} + /// Clears the value of `windDirection`. Subsequent reads from it will return its default value. + public mutating func clearWindDirection() {_uniqueStorage()._windDirection = nil} /// /// Wind speed in m/s public var windSpeed: Float { - get {return _storage._windSpeed} + get {return _storage._windSpeed ?? 0} set {_uniqueStorage()._windSpeed = newValue} } + /// Returns true if `windSpeed` has been explicitly set. + public var hasWindSpeed: Bool {return _storage._windSpeed != nil} + /// Clears the value of `windSpeed`. Subsequent reads from it will return its default value. + public mutating func clearWindSpeed() {_uniqueStorage()._windSpeed = nil} /// /// Weight in KG public var weight: Float { - get {return _storage._weight} + get {return _storage._weight ?? 0} set {_uniqueStorage()._weight = newValue} } + /// Returns true if `weight` has been explicitly set. + public var hasWeight: Bool {return _storage._weight != nil} + /// Clears the value of `weight`. Subsequent reads from it will return its default value. + public mutating func clearWeight() {_uniqueStorage()._weight = nil} /// /// Wind gust in m/s public var windGust: Float { - get {return _storage._windGust} + get {return _storage._windGust ?? 0} set {_uniqueStorage()._windGust = newValue} } + /// Returns true if `windGust` has been explicitly set. + public var hasWindGust: Bool {return _storage._windGust != nil} + /// Clears the value of `windGust`. Subsequent reads from it will return its default value. + public mutating func clearWindGust() {_uniqueStorage()._windGust = nil} /// /// Wind lull in m/s public var windLull: Float { - get {return _storage._windLull} + get {return _storage._windLull ?? 0} set {_uniqueStorage()._windLull = newValue} } + /// Returns true if `windLull` has been explicitly set. + public var hasWindLull: Bool {return _storage._windLull != nil} + /// Clears the value of `windLull`. Subsequent reads from it will return its default value. + public mutating func clearWindLull() {_uniqueStorage()._windLull = nil} public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -399,94 +535,284 @@ public struct EnvironmentMetrics: @unchecked Sendable { /// /// Power Metrics (voltage / current / etc) -public struct PowerMetrics: Sendable { +public struct PowerMetrics { // 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. /// /// Voltage (Ch1) - public var ch1Voltage: Float = 0 + public var ch1Voltage: Float { + get {return _ch1Voltage ?? 0} + set {_ch1Voltage = newValue} + } + /// Returns true if `ch1Voltage` has been explicitly set. + public var hasCh1Voltage: Bool {return self._ch1Voltage != nil} + /// Clears the value of `ch1Voltage`. Subsequent reads from it will return its default value. + public mutating func clearCh1Voltage() {self._ch1Voltage = nil} /// /// Current (Ch1) - public var ch1Current: Float = 0 + public var ch1Current: Float { + get {return _ch1Current ?? 0} + set {_ch1Current = newValue} + } + /// Returns true if `ch1Current` has been explicitly set. + public var hasCh1Current: Bool {return self._ch1Current != nil} + /// Clears the value of `ch1Current`. Subsequent reads from it will return its default value. + public mutating func clearCh1Current() {self._ch1Current = nil} /// /// Voltage (Ch2) - public var ch2Voltage: Float = 0 + public var ch2Voltage: Float { + get {return _ch2Voltage ?? 0} + set {_ch2Voltage = newValue} + } + /// Returns true if `ch2Voltage` has been explicitly set. + public var hasCh2Voltage: Bool {return self._ch2Voltage != nil} + /// Clears the value of `ch2Voltage`. Subsequent reads from it will return its default value. + public mutating func clearCh2Voltage() {self._ch2Voltage = nil} /// /// Current (Ch2) - public var ch2Current: Float = 0 + public var ch2Current: Float { + get {return _ch2Current ?? 0} + set {_ch2Current = newValue} + } + /// Returns true if `ch2Current` has been explicitly set. + public var hasCh2Current: Bool {return self._ch2Current != nil} + /// Clears the value of `ch2Current`. Subsequent reads from it will return its default value. + public mutating func clearCh2Current() {self._ch2Current = nil} /// /// Voltage (Ch3) - public var ch3Voltage: Float = 0 + public var ch3Voltage: Float { + get {return _ch3Voltage ?? 0} + set {_ch3Voltage = newValue} + } + /// Returns true if `ch3Voltage` has been explicitly set. + public var hasCh3Voltage: Bool {return self._ch3Voltage != nil} + /// Clears the value of `ch3Voltage`. Subsequent reads from it will return its default value. + public mutating func clearCh3Voltage() {self._ch3Voltage = nil} /// /// Current (Ch3) - public var ch3Current: Float = 0 + public var ch3Current: Float { + get {return _ch3Current ?? 0} + set {_ch3Current = newValue} + } + /// Returns true if `ch3Current` has been explicitly set. + public var hasCh3Current: Bool {return self._ch3Current != nil} + /// Clears the value of `ch3Current`. Subsequent reads from it will return its default value. + public mutating func clearCh3Current() {self._ch3Current = nil} public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _ch1Voltage: Float? = nil + fileprivate var _ch1Current: Float? = nil + fileprivate var _ch2Voltage: Float? = nil + fileprivate var _ch2Current: Float? = nil + fileprivate var _ch3Voltage: Float? = nil + fileprivate var _ch3Current: Float? = nil } /// /// Air quality metrics -public struct AirQualityMetrics: Sendable { +public struct AirQualityMetrics { // 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. /// /// Concentration Units Standard PM1.0 - public var pm10Standard: UInt32 = 0 + public var pm10Standard: UInt32 { + get {return _pm10Standard ?? 0} + set {_pm10Standard = newValue} + } + /// Returns true if `pm10Standard` has been explicitly set. + public var hasPm10Standard: Bool {return self._pm10Standard != nil} + /// Clears the value of `pm10Standard`. Subsequent reads from it will return its default value. + public mutating func clearPm10Standard() {self._pm10Standard = nil} /// /// Concentration Units Standard PM2.5 - public var pm25Standard: UInt32 = 0 + public var pm25Standard: UInt32 { + get {return _pm25Standard ?? 0} + set {_pm25Standard = newValue} + } + /// Returns true if `pm25Standard` has been explicitly set. + public var hasPm25Standard: Bool {return self._pm25Standard != nil} + /// Clears the value of `pm25Standard`. Subsequent reads from it will return its default value. + public mutating func clearPm25Standard() {self._pm25Standard = nil} /// /// Concentration Units Standard PM10.0 - public var pm100Standard: UInt32 = 0 + public var pm100Standard: UInt32 { + get {return _pm100Standard ?? 0} + set {_pm100Standard = newValue} + } + /// Returns true if `pm100Standard` has been explicitly set. + public var hasPm100Standard: Bool {return self._pm100Standard != nil} + /// Clears the value of `pm100Standard`. Subsequent reads from it will return its default value. + public mutating func clearPm100Standard() {self._pm100Standard = nil} /// /// Concentration Units Environmental PM1.0 - public var pm10Environmental: UInt32 = 0 + public var pm10Environmental: UInt32 { + get {return _pm10Environmental ?? 0} + set {_pm10Environmental = newValue} + } + /// Returns true if `pm10Environmental` has been explicitly set. + public var hasPm10Environmental: Bool {return self._pm10Environmental != nil} + /// Clears the value of `pm10Environmental`. Subsequent reads from it will return its default value. + public mutating func clearPm10Environmental() {self._pm10Environmental = nil} /// /// Concentration Units Environmental PM2.5 - public var pm25Environmental: UInt32 = 0 + public var pm25Environmental: UInt32 { + get {return _pm25Environmental ?? 0} + set {_pm25Environmental = newValue} + } + /// Returns true if `pm25Environmental` has been explicitly set. + public var hasPm25Environmental: Bool {return self._pm25Environmental != nil} + /// Clears the value of `pm25Environmental`. Subsequent reads from it will return its default value. + public mutating func clearPm25Environmental() {self._pm25Environmental = nil} /// /// Concentration Units Environmental PM10.0 - public var pm100Environmental: UInt32 = 0 + public var pm100Environmental: UInt32 { + get {return _pm100Environmental ?? 0} + set {_pm100Environmental = newValue} + } + /// Returns true if `pm100Environmental` has been explicitly set. + public var hasPm100Environmental: Bool {return self._pm100Environmental != nil} + /// Clears the value of `pm100Environmental`. Subsequent reads from it will return its default value. + public mutating func clearPm100Environmental() {self._pm100Environmental = nil} /// /// 0.3um Particle Count - public var particles03Um: UInt32 = 0 + public var particles03Um: UInt32 { + get {return _particles03Um ?? 0} + set {_particles03Um = newValue} + } + /// Returns true if `particles03Um` has been explicitly set. + public var hasParticles03Um: Bool {return self._particles03Um != nil} + /// Clears the value of `particles03Um`. Subsequent reads from it will return its default value. + public mutating func clearParticles03Um() {self._particles03Um = nil} /// /// 0.5um Particle Count - public var particles05Um: UInt32 = 0 + public var particles05Um: UInt32 { + get {return _particles05Um ?? 0} + set {_particles05Um = newValue} + } + /// Returns true if `particles05Um` has been explicitly set. + public var hasParticles05Um: Bool {return self._particles05Um != nil} + /// Clears the value of `particles05Um`. Subsequent reads from it will return its default value. + public mutating func clearParticles05Um() {self._particles05Um = nil} /// /// 1.0um Particle Count - public var particles10Um: UInt32 = 0 + public var particles10Um: UInt32 { + get {return _particles10Um ?? 0} + set {_particles10Um = newValue} + } + /// Returns true if `particles10Um` has been explicitly set. + public var hasParticles10Um: Bool {return self._particles10Um != nil} + /// Clears the value of `particles10Um`. Subsequent reads from it will return its default value. + public mutating func clearParticles10Um() {self._particles10Um = nil} /// /// 2.5um Particle Count - public var particles25Um: UInt32 = 0 + public var particles25Um: UInt32 { + get {return _particles25Um ?? 0} + set {_particles25Um = newValue} + } + /// Returns true if `particles25Um` has been explicitly set. + public var hasParticles25Um: Bool {return self._particles25Um != nil} + /// Clears the value of `particles25Um`. Subsequent reads from it will return its default value. + public mutating func clearParticles25Um() {self._particles25Um = nil} /// /// 5.0um Particle Count - public var particles50Um: UInt32 = 0 + public var particles50Um: UInt32 { + get {return _particles50Um ?? 0} + set {_particles50Um = newValue} + } + /// Returns true if `particles50Um` has been explicitly set. + public var hasParticles50Um: Bool {return self._particles50Um != nil} + /// Clears the value of `particles50Um`. Subsequent reads from it will return its default value. + public mutating func clearParticles50Um() {self._particles50Um = nil} /// /// 10.0um Particle Count - public var particles100Um: UInt32 = 0 + public var particles100Um: UInt32 { + get {return _particles100Um ?? 0} + set {_particles100Um = newValue} + } + /// Returns true if `particles100Um` has been explicitly set. + public var hasParticles100Um: Bool {return self._particles100Um != nil} + /// Clears the value of `particles100Um`. Subsequent reads from it will return its default value. + public mutating func clearParticles100Um() {self._particles100Um = 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 +} + +/// +/// Local device mesh statistics +public struct LocalStats { + // 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. + + /// + /// How long the device has been running since the last reboot (in seconds) + public var uptimeSeconds: UInt32 = 0 + + /// + /// Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise). + public var channelUtilization: Float = 0 + + /// + /// Percent of airtime for transmission used within the last hour. + public var airUtilTx: Float = 0 + + /// + /// Number of packets sent + public var numPacketsTx: UInt32 = 0 + + /// + /// Number of packets received good + public var numPacketsRx: UInt32 = 0 + + /// + /// Number of packets received that are malformed or violate the protocol + public var numPacketsRxBad: UInt32 = 0 + + /// + /// Number of nodes online (in the past 2 hours) + public var numOnlineNodes: UInt32 = 0 + + /// + /// Number of nodes total + public var numTotalNodes: UInt32 = 0 public var unknownFields = SwiftProtobuf.UnknownStorage() @@ -495,7 +821,7 @@ public struct AirQualityMetrics: Sendable { /// /// Types of Measurements the telemetry module is equipped to handle -public struct Telemetry: Sendable { +public struct Telemetry { // 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. @@ -546,9 +872,19 @@ public struct Telemetry: Sendable { set {variant = .powerMetrics(newValue)} } + /// + /// Local device mesh statistics + public var localStats: LocalStats { + get { + if case .localStats(let v)? = variant {return v} + return LocalStats() + } + set {variant = .localStats(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum OneOf_Variant: Equatable, Sendable { + public enum OneOf_Variant: Equatable { /// /// Key native device metrics such as battery level case deviceMetrics(DeviceMetrics) @@ -561,7 +897,40 @@ public struct Telemetry: Sendable { /// /// Power Metrics case powerMetrics(PowerMetrics) + /// + /// Local device mesh statistics + case localStats(LocalStats) + #if !swift(>=4.1) + public static func ==(lhs: Telemetry.OneOf_Variant, rhs: Telemetry.OneOf_Variant) -> Bool { + // 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 (lhs, rhs) { + case (.deviceMetrics, .deviceMetrics): return { + guard case .deviceMetrics(let l) = lhs, case .deviceMetrics(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.environmentMetrics, .environmentMetrics): return { + guard case .environmentMetrics(let l) = lhs, case .environmentMetrics(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.airQualityMetrics, .airQualityMetrics): return { + guard case .airQualityMetrics(let l) = lhs, case .airQualityMetrics(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.powerMetrics, .powerMetrics): return { + guard case .powerMetrics(let l) = lhs, case .powerMetrics(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.localStats, .localStats): return { + guard case .localStats(let l) = lhs, case .localStats(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif } public init() {} @@ -569,7 +938,7 @@ public struct Telemetry: Sendable { /// /// NAU7802 Telemetry configuration, for saving to flash -public struct Nau7802Config: Sendable { +public struct Nau7802Config { // 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. @@ -587,6 +956,18 @@ public struct Nau7802Config: Sendable { public init() {} } +#if swift(>=5.5) && canImport(_Concurrency) +extension TelemetrySensorType: @unchecked Sendable {} +extension DeviceMetrics: @unchecked Sendable {} +extension EnvironmentMetrics: @unchecked Sendable {} +extension PowerMetrics: @unchecked Sendable {} +extension AirQualityMetrics: @unchecked Sendable {} +extension LocalStats: @unchecked Sendable {} +extension Telemetry: @unchecked Sendable {} +extension Telemetry.OneOf_Variant: @unchecked Sendable {} +extension Nau7802Config: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" @@ -619,6 +1000,9 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding { 23: .same(proto: "AHT10"), 24: .same(proto: "DFROBOT_LARK"), 25: .same(proto: "NAU7802"), + 26: .same(proto: "BMP3XX"), + 27: .same(proto: "ICM20948"), + 28: .same(proto: "MAX17048"), ] } @@ -638,41 +1022,45 @@ extension DeviceMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa // 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.batteryLevel) }() - case 2: try { try decoder.decodeSingularFloatField(value: &self.voltage) }() - case 3: try { try decoder.decodeSingularFloatField(value: &self.channelUtilization) }() - case 4: try { try decoder.decodeSingularFloatField(value: &self.airUtilTx) }() - case 5: try { try decoder.decodeSingularUInt32Field(value: &self.uptimeSeconds) }() + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._batteryLevel) }() + case 2: try { try decoder.decodeSingularFloatField(value: &self._voltage) }() + case 3: try { try decoder.decodeSingularFloatField(value: &self._channelUtilization) }() + case 4: try { try decoder.decodeSingularFloatField(value: &self._airUtilTx) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self._uptimeSeconds) }() default: break } } } public func traverse(visitor: inout V) throws { - if self.batteryLevel != 0 { - try visitor.visitSingularUInt32Field(value: self.batteryLevel, fieldNumber: 1) - } - if self.voltage.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.voltage, fieldNumber: 2) - } - if self.channelUtilization.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.channelUtilization, fieldNumber: 3) - } - if self.airUtilTx.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.airUtilTx, fieldNumber: 4) - } - if self.uptimeSeconds != 0 { - try visitor.visitSingularUInt32Field(value: self.uptimeSeconds, fieldNumber: 5) - } + // 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._batteryLevel { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + try { if let v = self._voltage { + try visitor.visitSingularFloatField(value: v, fieldNumber: 2) + } }() + try { if let v = self._channelUtilization { + try visitor.visitSingularFloatField(value: v, fieldNumber: 3) + } }() + try { if let v = self._airUtilTx { + try visitor.visitSingularFloatField(value: v, fieldNumber: 4) + } }() + try { if let v = self._uptimeSeconds { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: DeviceMetrics, rhs: DeviceMetrics) -> Bool { - if lhs.batteryLevel != rhs.batteryLevel {return false} - if lhs.voltage != rhs.voltage {return false} - if lhs.channelUtilization != rhs.channelUtilization {return false} - if lhs.airUtilTx != rhs.airUtilTx {return false} - if lhs.uptimeSeconds != rhs.uptimeSeconds {return false} + if lhs._batteryLevel != rhs._batteryLevel {return false} + if lhs._voltage != rhs._voltage {return false} + if lhs._channelUtilization != rhs._channelUtilization {return false} + if lhs._airUtilTx != rhs._airUtilTx {return false} + if lhs._uptimeSeconds != rhs._uptimeSeconds {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -701,23 +1089,23 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple ] fileprivate class _StorageClass { - var _temperature: Float = 0 - var _relativeHumidity: Float = 0 - var _barometricPressure: Float = 0 - var _gasResistance: Float = 0 - var _voltage: Float = 0 - var _current: Float = 0 - var _iaq: UInt32 = 0 - var _distance: Float = 0 - var _lux: Float = 0 - var _whiteLux: Float = 0 - var _irLux: Float = 0 - var _uvLux: Float = 0 - var _windDirection: UInt32 = 0 - var _windSpeed: Float = 0 - var _weight: Float = 0 - var _windGust: Float = 0 - var _windLull: Float = 0 + var _temperature: Float? = nil + var _relativeHumidity: Float? = nil + var _barometricPressure: Float? = nil + var _gasResistance: Float? = nil + var _voltage: Float? = nil + var _current: Float? = nil + var _iaq: UInt32? = nil + var _distance: Float? = nil + var _lux: Float? = nil + var _whiteLux: Float? = nil + var _irLux: Float? = nil + var _uvLux: Float? = nil + var _windDirection: UInt32? = nil + var _windSpeed: Float? = nil + var _weight: Float? = nil + var _windGust: Float? = nil + var _windLull: Float? = nil #if swift(>=5.10) // This property is used as the initial default value for new instances of the type. @@ -792,57 +1180,61 @@ extension EnvironmentMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImple public func traverse(visitor: inout V) throws { try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - if _storage._temperature.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._temperature, fieldNumber: 1) - } - if _storage._relativeHumidity.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._relativeHumidity, fieldNumber: 2) - } - if _storage._barometricPressure.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._barometricPressure, fieldNumber: 3) - } - if _storage._gasResistance.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._gasResistance, fieldNumber: 4) - } - if _storage._voltage.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._voltage, fieldNumber: 5) - } - if _storage._current.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._current, fieldNumber: 6) - } - if _storage._iaq != 0 { - try visitor.visitSingularUInt32Field(value: _storage._iaq, fieldNumber: 7) - } - if _storage._distance.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._distance, fieldNumber: 8) - } - if _storage._lux.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._lux, fieldNumber: 9) - } - if _storage._whiteLux.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._whiteLux, fieldNumber: 10) - } - if _storage._irLux.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._irLux, fieldNumber: 11) - } - if _storage._uvLux.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._uvLux, fieldNumber: 12) - } - if _storage._windDirection != 0 { - try visitor.visitSingularUInt32Field(value: _storage._windDirection, fieldNumber: 13) - } - if _storage._windSpeed.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._windSpeed, fieldNumber: 14) - } - if _storage._weight.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._weight, fieldNumber: 15) - } - if _storage._windGust.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._windGust, fieldNumber: 16) - } - if _storage._windLull.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._windLull, fieldNumber: 17) - } + // 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._temperature { + try visitor.visitSingularFloatField(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._relativeHumidity { + try visitor.visitSingularFloatField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._barometricPressure { + try visitor.visitSingularFloatField(value: v, fieldNumber: 3) + } }() + try { if let v = _storage._gasResistance { + try visitor.visitSingularFloatField(value: v, fieldNumber: 4) + } }() + try { if let v = _storage._voltage { + try visitor.visitSingularFloatField(value: v, fieldNumber: 5) + } }() + try { if let v = _storage._current { + try visitor.visitSingularFloatField(value: v, fieldNumber: 6) + } }() + try { if let v = _storage._iaq { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 7) + } }() + try { if let v = _storage._distance { + try visitor.visitSingularFloatField(value: v, fieldNumber: 8) + } }() + try { if let v = _storage._lux { + try visitor.visitSingularFloatField(value: v, fieldNumber: 9) + } }() + try { if let v = _storage._whiteLux { + try visitor.visitSingularFloatField(value: v, fieldNumber: 10) + } }() + try { if let v = _storage._irLux { + try visitor.visitSingularFloatField(value: v, fieldNumber: 11) + } }() + try { if let v = _storage._uvLux { + try visitor.visitSingularFloatField(value: v, fieldNumber: 12) + } }() + try { if let v = _storage._windDirection { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 13) + } }() + try { if let v = _storage._windSpeed { + try visitor.visitSingularFloatField(value: v, fieldNumber: 14) + } }() + try { if let v = _storage._weight { + try visitor.visitSingularFloatField(value: v, fieldNumber: 15) + } }() + try { if let v = _storage._windGust { + try visitor.visitSingularFloatField(value: v, fieldNumber: 16) + } }() + try { if let v = _storage._windLull { + try visitor.visitSingularFloatField(value: v, fieldNumber: 17) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -895,46 +1287,50 @@ extension PowerMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat // 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.decodeSingularFloatField(value: &self.ch1Voltage) }() - case 2: try { try decoder.decodeSingularFloatField(value: &self.ch1Current) }() - case 3: try { try decoder.decodeSingularFloatField(value: &self.ch2Voltage) }() - case 4: try { try decoder.decodeSingularFloatField(value: &self.ch2Current) }() - case 5: try { try decoder.decodeSingularFloatField(value: &self.ch3Voltage) }() - case 6: try { try decoder.decodeSingularFloatField(value: &self.ch3Current) }() + case 1: try { try decoder.decodeSingularFloatField(value: &self._ch1Voltage) }() + case 2: try { try decoder.decodeSingularFloatField(value: &self._ch1Current) }() + case 3: try { try decoder.decodeSingularFloatField(value: &self._ch2Voltage) }() + case 4: try { try decoder.decodeSingularFloatField(value: &self._ch2Current) }() + case 5: try { try decoder.decodeSingularFloatField(value: &self._ch3Voltage) }() + case 6: try { try decoder.decodeSingularFloatField(value: &self._ch3Current) }() default: break } } } public func traverse(visitor: inout V) throws { - if self.ch1Voltage.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.ch1Voltage, fieldNumber: 1) - } - if self.ch1Current.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.ch1Current, fieldNumber: 2) - } - if self.ch2Voltage.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.ch2Voltage, fieldNumber: 3) - } - if self.ch2Current.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.ch2Current, fieldNumber: 4) - } - if self.ch3Voltage.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.ch3Voltage, fieldNumber: 5) - } - if self.ch3Current.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.ch3Current, fieldNumber: 6) - } + // 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._ch1Voltage { + try visitor.visitSingularFloatField(value: v, fieldNumber: 1) + } }() + try { if let v = self._ch1Current { + try visitor.visitSingularFloatField(value: v, fieldNumber: 2) + } }() + try { if let v = self._ch2Voltage { + try visitor.visitSingularFloatField(value: v, fieldNumber: 3) + } }() + try { if let v = self._ch2Current { + try visitor.visitSingularFloatField(value: v, fieldNumber: 4) + } }() + try { if let v = self._ch3Voltage { + try visitor.visitSingularFloatField(value: v, fieldNumber: 5) + } }() + try { if let v = self._ch3Current { + try visitor.visitSingularFloatField(value: v, fieldNumber: 6) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: PowerMetrics, rhs: PowerMetrics) -> Bool { - if lhs.ch1Voltage != rhs.ch1Voltage {return false} - if lhs.ch1Current != rhs.ch1Current {return false} - if lhs.ch2Voltage != rhs.ch2Voltage {return false} - if lhs.ch2Current != rhs.ch2Current {return false} - if lhs.ch3Voltage != rhs.ch3Voltage {return false} - if lhs.ch3Current != rhs.ch3Current {return false} + if lhs._ch1Voltage != rhs._ch1Voltage {return false} + if lhs._ch1Current != rhs._ch1Current {return false} + if lhs._ch2Voltage != rhs._ch2Voltage {return false} + if lhs._ch2Current != rhs._ch2Current {return false} + if lhs._ch3Voltage != rhs._ch3Voltage {return false} + if lhs._ch3Current != rhs._ch3Current {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -963,76 +1359,154 @@ extension AirQualityMetrics: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem // 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 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) }() default: break } } } public func traverse(visitor: inout V) throws { - if self.pm10Standard != 0 { - try visitor.visitSingularUInt32Field(value: self.pm10Standard, fieldNumber: 1) - } - if self.pm25Standard != 0 { - try visitor.visitSingularUInt32Field(value: self.pm25Standard, fieldNumber: 2) - } - if self.pm100Standard != 0 { - try visitor.visitSingularUInt32Field(value: self.pm100Standard, fieldNumber: 3) - } - if self.pm10Environmental != 0 { - try visitor.visitSingularUInt32Field(value: self.pm10Environmental, fieldNumber: 4) - } - if self.pm25Environmental != 0 { - try visitor.visitSingularUInt32Field(value: self.pm25Environmental, fieldNumber: 5) - } - if self.pm100Environmental != 0 { - try visitor.visitSingularUInt32Field(value: self.pm100Environmental, fieldNumber: 6) - } - if self.particles03Um != 0 { - try visitor.visitSingularUInt32Field(value: self.particles03Um, fieldNumber: 7) - } - if self.particles05Um != 0 { - try visitor.visitSingularUInt32Field(value: self.particles05Um, fieldNumber: 8) - } - if self.particles10Um != 0 { - try visitor.visitSingularUInt32Field(value: self.particles10Um, fieldNumber: 9) - } - if self.particles25Um != 0 { - try visitor.visitSingularUInt32Field(value: self.particles25Um, fieldNumber: 10) - } - if self.particles50Um != 0 { - try visitor.visitSingularUInt32Field(value: self.particles50Um, fieldNumber: 11) - } - if self.particles100Um != 0 { - try visitor.visitSingularUInt32Field(value: self.particles100Um, fieldNumber: 12) - } + // 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 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._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.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension LocalStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LocalStats" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "uptime_seconds"), + 2: .standard(proto: "channel_utilization"), + 3: .standard(proto: "air_util_tx"), + 4: .standard(proto: "num_packets_tx"), + 5: .standard(proto: "num_packets_rx"), + 6: .standard(proto: "num_packets_rx_bad"), + 7: .standard(proto: "num_online_nodes"), + 8: .standard(proto: "num_total_nodes"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self.uptimeSeconds) }() + case 2: try { try decoder.decodeSingularFloatField(value: &self.channelUtilization) }() + case 3: try { try decoder.decodeSingularFloatField(value: &self.airUtilTx) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.numPacketsTx) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.numPacketsRx) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self.numPacketsRxBad) }() + case 7: try { try decoder.decodeSingularUInt32Field(value: &self.numOnlineNodes) }() + case 8: try { try decoder.decodeSingularUInt32Field(value: &self.numTotalNodes) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.uptimeSeconds != 0 { + try visitor.visitSingularUInt32Field(value: self.uptimeSeconds, fieldNumber: 1) + } + if self.channelUtilization != 0 { + try visitor.visitSingularFloatField(value: self.channelUtilization, fieldNumber: 2) + } + if self.airUtilTx != 0 { + try visitor.visitSingularFloatField(value: self.airUtilTx, fieldNumber: 3) + } + if self.numPacketsTx != 0 { + try visitor.visitSingularUInt32Field(value: self.numPacketsTx, fieldNumber: 4) + } + if self.numPacketsRx != 0 { + try visitor.visitSingularUInt32Field(value: self.numPacketsRx, fieldNumber: 5) + } + if self.numPacketsRxBad != 0 { + try visitor.visitSingularUInt32Field(value: self.numPacketsRxBad, fieldNumber: 6) + } + if self.numOnlineNodes != 0 { + try visitor.visitSingularUInt32Field(value: self.numOnlineNodes, fieldNumber: 7) + } + if self.numTotalNodes != 0 { + try visitor.visitSingularUInt32Field(value: self.numTotalNodes, fieldNumber: 8) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: LocalStats, rhs: LocalStats) -> Bool { + if lhs.uptimeSeconds != rhs.uptimeSeconds {return false} + if lhs.channelUtilization != rhs.channelUtilization {return false} + if lhs.airUtilTx != rhs.airUtilTx {return false} + if lhs.numPacketsTx != rhs.numPacketsTx {return false} + if lhs.numPacketsRx != rhs.numPacketsRx {return false} + if lhs.numPacketsRxBad != rhs.numPacketsRxBad {return false} + if lhs.numOnlineNodes != rhs.numOnlineNodes {return false} + if lhs.numTotalNodes != rhs.numTotalNodes {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1046,6 +1520,7 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 3: .standard(proto: "environment_metrics"), 4: .standard(proto: "air_quality_metrics"), 5: .standard(proto: "power_metrics"), + 6: .standard(proto: "local_stats"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -1107,6 +1582,19 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation self.variant = .powerMetrics(v) } }() + case 6: try { + var v: LocalStats? + var hadOneofValue = false + if let current = self.variant { + hadOneofValue = true + if case .localStats(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.variant = .localStats(v) + } + }() default: break } } @@ -1137,6 +1625,10 @@ extension Telemetry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation guard case .powerMetrics(let v)? = self.variant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 5) }() + case .localStats?: try { + guard case .localStats(let v)? = self.variant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -1174,7 +1666,7 @@ extension Nau7802Config: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if self.zeroOffset != 0 { try visitor.visitSingularInt32Field(value: self.zeroOffset, fieldNumber: 1) } - if self.calibrationFactor.bitPattern != 0 { + if self.calibrationFactor != 0 { try visitor.visitSingularFloatField(value: self.calibrationFactor, fieldNumber: 2) } try unknownFields.traverse(visitor: &visitor) diff --git a/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift index 89d0097c..1f41fe0b 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/xmodem.pb.swift @@ -20,7 +20,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } -public struct XModem: @unchecked Sendable { +public struct XModem { // 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. @@ -35,7 +35,7 @@ public struct XModem: @unchecked Sendable { public var unknownFields = SwiftProtobuf.UnknownStorage() - public enum Control: SwiftProtobuf.Enum, Swift.CaseIterable { + public enum Control: SwiftProtobuf.Enum { public typealias RawValue = Int case nul // = 0 case soh // = 1 @@ -79,23 +79,34 @@ public struct XModem: @unchecked Sendable { } } - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [XModem.Control] = [ - .nul, - .soh, - .stx, - .eot, - .ack, - .nak, - .can, - .ctrlz, - ] - } public init() {} } +#if swift(>=4.2) + +extension XModem.Control: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [XModem.Control] = [ + .nul, + .soh, + .stx, + .eot, + .ack, + .nak, + .can, + .ctrlz, + ] +} + +#endif // swift(>=4.2) + +#if swift(>=5.5) && canImport(_Concurrency) +extension XModem: @unchecked Sendable {} +extension XModem.Control: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "meshtastic" diff --git a/protobufs b/protobufs index 2fa7d6a4..b6237629 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 2fa7d6a4b702fcd58b54b0d1d6e4b3b85164f649 +Subproject commit b623762940ebdb1887a3b31b86f4d9cdaa7e6ecf diff --git a/scripts/gen_protos.sh b/scripts/gen_protos.sh index a587a8e3..d07bc798 100755 --- a/scripts/gen_protos.sh +++ b/scripts/gen_protos.sh @@ -1,12 +1,5 @@ #!/bin/bash -# simple sanity checking for repo -if [ ! -d "./protobufs" ]; then - git submodule update --init -else - git submodule update --remote --merge -fi - # simple sanity checking for executable if [ ! -x "$(which protoc)" ]; then brew install swift-protobuf