diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 7b68ae40..d019ab3e 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -1512,6 +1512,9 @@ } } } + }, + "12 Hour Clock" : { + }, "25" : { "localizations" : { @@ -2809,34 +2812,6 @@ } } }, - "Allow incoming device control over the insecure legacy admin channel." : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erlaubt die eingehende Gerätesteuerung über den unsicheren Legacy-Admin-Kanal." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Consentire il controllo del dispositivo in entrata attraverso il canale di amministrazione legacy non sicuro." - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Дозволите контролу долазног уређаја над небезбедним старим администраторским каналом." - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "允許經由不安全的傳統管理通道接收裝置控制指令。" - } - } - } - }, "Allow Position Requests" : { "localizations" : { "it" : { @@ -11316,6 +11291,9 @@ } } } + }, + "Expiration" : { + }, "Expire" : { "localizations" : { @@ -13243,6 +13221,9 @@ } } } + }, + "GitHub Repository" : { + }, "Good" : { "localizations" : { @@ -13816,34 +13797,6 @@ } } }, - "Help with App Development" : { - "localizations" : { - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aiuto per lo sviluppo di app" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Помози при развоју апликације" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "帮助开发应用程序" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "幫助App開發" - } - } - } - }, "Hide alerts" : { "localizations" : { "it" : { @@ -15650,6 +15603,12 @@ } } } + }, + "Latitude in degrees (e.g., 37.7749)" : { + + }, + "Latitude must be between -90 and 90 degrees" : { + }, "LED Heartbeat" : { "localizations" : { @@ -15765,34 +15724,6 @@ } } }, - "Legacy Administration" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alte Administrationsart" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Amministrazione del patrimonio" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Стари начин администрације" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "舊版遠端管理" - } - } - } - }, "Level" : { "localizations" : { "de" : { @@ -15957,40 +15888,6 @@ } } }, - "Location" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standort" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Posizione" - } - }, - "sr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Локација:" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "位置" - } - }, - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "位置" - } - } - } - }, "Location:" : { "localizations" : { "de" : { @@ -16396,6 +16293,12 @@ } } } + }, + "Longitude in degrees (e.g., -122.4194)" : { + + }, + "Longitude must be between -180 and 180 degrees" : { + }, "LoRa" : { "localizations" : { @@ -23974,6 +23877,9 @@ } } } + }, + "Regenerate Private Key" : { + }, "Region" : { "localizations" : { @@ -24353,17 +24259,6 @@ } } }, - "Replying to a message" : { - "extractionState" : "stale", - "localizations" : { - "zh-Hant-TW" : { - "stringUnit" : { - "state" : "translated", - "value" : "正在回覆訊息" - } - } - } - }, "Request Legacy Admin: %@" : { "localizations" : { "it" : { @@ -27776,6 +27671,9 @@ } } } + }, + "Set to current location" : { + }, "Sets the maximum number of hops, default is 3. Increasing hops also increases congestion and should be used carefully. O hop broadcast messages will not get ACKs." : { "localizations" : { @@ -27798,6 +27696,9 @@ } } } + }, + "Sets the screen clock format to 12-hour." : { + }, "Settings" : { "localizations" : { @@ -28970,6 +28871,9 @@ } } } + }, + "Sponsor App Development" : { + }, "Spread Factor" : { "localizations" : { @@ -33330,6 +33234,9 @@ } } } + }, + "Use my Location" : { + }, "Use Preset" : { "localizations" : { @@ -34157,6 +34064,9 @@ } } } + }, + "Waypoint Failed to Send" : { + }, "Waypoint Options" : { "localizations" : { diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6b37e6a6..58b50820 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -371,6 +371,7 @@ 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 = ""; }; + DD1BEF462DFF284C0090CE24 /* MeshtasticDataModelV 53.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModelV 53.xcdatamodel"; 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 = ""; }; @@ -1802,12 +1803,12 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.6.5; + MARKETING_VERSION = 2.6.6; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1835,12 +1836,12 @@ INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.6.5; + MARKETING_VERSION = 2.6.6; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1865,13 +1866,13 @@ INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.6.5; + MARKETING_VERSION = 2.6.6; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1897,13 +1898,13 @@ INFOPLIST_FILE = Widgets/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Widgets; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; + IPHONEOS_DEPLOYMENT_TARGET = 17.3; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 2.6.5; + MARKETING_VERSION = 2.6.6; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2002,6 +2003,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD1BEF462DFF284C0090CE24 /* MeshtasticDataModelV 53.xcdatamodel */, DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */, DD63CB4E2DD4FBEA00AFCAE2 /* MeshtasticDataModelV 51.xcdatamodel */, 233E99B32D84969500CC3A77 /* MeshtasticDataModelV 50.xcdatamodel */, @@ -2055,7 +2057,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DD0836AB2DE7C7CB00A3A973 /* MeshtasticDataModelV 52.xcdatamodel */; + currentVersion = DD1BEF462DFF284C0090CE24 /* MeshtasticDataModelV 53.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/AppIntents/RestartNodeIntent.swift b/Meshtastic/AppIntents/RestartNodeIntent.swift index 5f317a28..3859c114 100644 --- a/Meshtastic/AppIntents/RestartNodeIntent.swift +++ b/Meshtastic/AppIntents/RestartNodeIntent.swift @@ -24,11 +24,10 @@ struct RestartNodeIntent: AppIntent { if let connectedPeripheralNum = BLEManager.shared.connectedPeripheral?.num, let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), let fromUser = connectedNode.user, - let toUser = connectedNode.user, - let adminIndex = connectedNode.myInfo?.adminIndex { + let toUser = connectedNode.user { // Attempt to send shutdown, throw an error if it fails - if !BLEManager.shared.sendReboot(fromUser: fromUser, toUser: toUser, adminIndex: adminIndex) { + if !BLEManager.shared.sendReboot(fromUser: fromUser, toUser: toUser) { throw AppIntentErrors.AppIntentError.message("Failed to restart") } } else { diff --git a/Meshtastic/AppIntents/SendWaypointIntent.swift b/Meshtastic/AppIntents/SendWaypointIntent.swift index 4352c548..fb0f97c3 100644 --- a/Meshtastic/AppIntents/SendWaypointIntent.swift +++ b/Meshtastic/AppIntents/SendWaypointIntent.swift @@ -11,6 +11,8 @@ import AppIntents import MeshtasticProtobufs struct SendWaypointIntent: AppIntent { + + var defaultDate = Date.now.addingTimeInterval(60 * 480) static var title = LocalizedStringResource("Send a Waypoint") @@ -23,13 +25,24 @@ struct SendWaypointIntent: AppIntent { @Parameter(title: "Emoji", default: "📍") var emojiParameter: String? - @Parameter(title: "Location") - var locationParameter: CLPlacemark + // Replace CLPlacemark with latitude and longitude parameters + @Parameter(title: "Latitude", description: "Latitude in degrees (e.g., 37.7749)") + var latitudeParameter: Double + + @Parameter(title: "Longitude", description: "Longitude in degrees (e.g., -122.4194)") + var longitudeParameter: Double + + @Parameter(title: "Locked", default: false) + var isLocked: Bool + + @Parameter(title: "Expiration") + var expiration: Date? func perform() async throws -> some IntentResult { if !BLEManager.shared.isConnected { throw AppIntentErrors.AppIntentError.notConnected } + // Provide default values if parameters are nil let name = nameParameter ?? "Dropped Pin" let description = descriptionParameter ?? "" @@ -50,24 +63,39 @@ struct SendWaypointIntent: AppIntent { throw $emojiParameter.needsValueError("Must be a single emoji") } + // Validate latitude and longitude + guard abs(latitudeParameter) <= 90 else { + throw $latitudeParameter.needsValueError("Latitude must be between -90 and 90 degrees") + } + guard abs(longitudeParameter) <= 180 else { + throw $longitudeParameter.needsValueError("Longitude must be between -180 and 180 degrees") + } + var newWaypoint = Waypoint() - if let latitude = locationParameter.location?.coordinate.latitude { - newWaypoint.latitudeI = Int32(latitude * 10_000_000) - } - - if let longitude = locationParameter.location?.coordinate.longitude { - newWaypoint.longitudeI = Int32(longitude * 10_000_000) - } + // Set latitude and longitude directly + newWaypoint.latitudeI = Int32(latitudeParameter * 10_000_000) + newWaypoint.longitudeI = Int32(longitudeParameter * 10_000_000) newWaypoint.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - // This regex pattern is for matching a single emoji let emojiPattern = "^([\\p{So}\\p{Cn}])$" let regex = try? NSRegularExpression(pattern: emojiPattern, options: []) let matches = regex?.matches(in: emoji, options: [], range: NSRange(location: 0, length: emoji.utf16.count)) - return matches?.count == 1 } } diff --git a/Meshtastic/AppIntents/ShutDownNodeIntent.swift b/Meshtastic/AppIntents/ShutDownNodeIntent.swift index dcb43f3c..7f5acc57 100644 --- a/Meshtastic/AppIntents/ShutDownNodeIntent.swift +++ b/Meshtastic/AppIntents/ShutDownNodeIntent.swift @@ -24,11 +24,10 @@ struct ShutDownNodeIntent: AppIntent { if let connectedPeripheralNum = BLEManager.shared.connectedPeripheral?.num, let connectedNode = getNodeInfo(id: connectedPeripheralNum, context: PersistenceController.shared.container.viewContext), let fromUser = connectedNode.user, - let toUser = connectedNode.user, - let adminIndex = connectedNode.myInfo?.adminIndex { + let toUser = connectedNode.user { // Attempt to send shutdown, throw an error if it fails - if !BLEManager.shared.sendShutdown(fromUser: fromUser, toUser: toUser, adminIndex: adminIndex) { + if !BLEManager.shared.sendShutdown(fromUser: fromUser, toUser: toUser) { throw AppIntentErrors.AppIntentError.message("Failed to shut down") } } else { diff --git a/Meshtastic/Assets.xcassets/HELTECMESHPOCKET.imageset/Contents.json b/Meshtastic/Assets.xcassets/HELTECMESHPOCKET.imageset/Contents.json new file mode 100644 index 00000000..4234e370 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECMESHPOCKET.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "heltec_mesh_pocket.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/HELTECMESHPOCKET.imageset/heltec_mesh_pocket.svg b/Meshtastic/Assets.xcassets/HELTECMESHPOCKET.imageset/heltec_mesh_pocket.svg new file mode 100644 index 00000000..1af4f5c6 --- /dev/null +++ b/Meshtastic/Assets.xcassets/HELTECMESHPOCKET.imageset/heltec_mesh_pocket.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Meshtastic/Assets.xcassets/SEEEDSOLARNODE.imageset/Contents.json b/Meshtastic/Assets.xcassets/SEEEDSOLARNODE.imageset/Contents.json new file mode 100644 index 00000000..d1ac2a26 --- /dev/null +++ b/Meshtastic/Assets.xcassets/SEEEDSOLARNODE.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "seeed_solar.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Meshtastic/Assets.xcassets/SEEEDSOLARNODE.imageset/seeed_solar.svg b/Meshtastic/Assets.xcassets/SEEEDSOLARNODE.imageset/seeed_solar.svg new file mode 100644 index 00000000..3f2b5d47 --- /dev/null +++ b/Meshtastic/Assets.xcassets/SEEEDSOLARNODE.imageset/seeed_solar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift index 4030ea6b..92f097b3 100644 --- a/Meshtastic/Extensions/CoreData/UserEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/UserEntityExtension.swift @@ -106,14 +106,44 @@ extension UserEntity { } } } -public func createUser(num: Int64, context: NSManagedObjectContext) -> UserEntity { - let newUser = UserEntity(context: context) - newUser.num = Int64(num) - let userId = String(format: "%2X", num) - newUser.userId = "!\(userId)" - let last4 = String(userId.suffix(4)) - newUser.longName = "Meshtastic \(last4)" - newUser.shortName = last4 - newUser.hwModel = "UNSET" + +public func createUser(num: Int64, context: NSManagedObjectContext) throws -> UserEntity { + // Validate Input + guard num >= 0 else { + throw CoreDataError.invalidInput(message: "User number cannot be negative.") + } + + var newUser: UserEntity! // Use an implicitly unwrapped optional, but ensure it's assigned + + context.performAndWait { + newUser = UserEntity(context: context) + newUser.num = num + + let userId = String(format: "%016llX", num) + newUser.userId = "!\(userId)" + + let last4 = String(userId.suffix(4)) + newUser.longName = "Meshtastic \(last4)" + newUser.shortName = last4 + newUser.hwModel = "UNSET" + } + return newUser } + +enum CoreDataError: Error, LocalizedError { + case invalidInput(message: String) + case saveFailed(message: String) + case entityCreationFailed(message: String) // In case UserEntity(context:) fails for some reason + + var errorDescription: String? { + switch self { + case .invalidInput(let message): + return "Core Data Input Error: \(message)" + case .saveFailed(let message): + return "Core Data Save Error: \(message)" + case .entityCreationFailed(let message): + return "Core Data Entity Creation Error: \(message)" + } + } +} diff --git a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift index 2f538b62..4f2923eb 100644 --- a/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift +++ b/Meshtastic/Extensions/CoreData/WaypointEntityExtension.swift @@ -14,9 +14,6 @@ extension WaypointEntity { static func allWaypointssFetchRequest() -> NSFetchRequest { let request: NSFetchRequest = WaypointEntity.fetchRequest() request.fetchLimit = 50 - // request.fetchBatchSize = 1 - // request.returnsObjectsAsFaults = false - // request.includesSubentities = true request.returnsDistinctResults = true request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: false)] request.predicate = NSPredicate(format: "expire == nil || expire >= %@", Date() as NSDate) @@ -24,7 +21,6 @@ extension WaypointEntity { } var latitude: Double? { - let d = Double(latitudeI) if d == 0 { return 0 @@ -33,7 +29,6 @@ extension WaypointEntity { } var longitude: Double? { - let d = Double(longitudeI) if d == 0 { return 0 @@ -46,7 +41,7 @@ extension WaypointEntity { let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!) return coord } else { - return nil + return nil } } @@ -60,16 +55,29 @@ extension WaypointEntity { } extension WaypointEntity: MKAnnotation { - public var coordinate: CLLocationCoordinate2D { waypointCoordinate ?? LocationsHandler.DefaultLocation } - public var title: String? { name ?? "Dropped Pin" } + @MainActor + public var coordinate: CLLocationCoordinate2D { + get { + waypointCoordinate ?? LocationsHandler.DefaultLocation + } + set { + latitudeI = Int32(newValue.latitude * 1e7) + longitudeI = Int32(newValue.longitude * 1e7) + } + } + + public var title: String? { + name ?? "Dropped Pin" + } + public var subtitle: String? { (longDescription ?? "") + String(expire != nil ? "\n⌛ Expires \(String(describing: expire?.formatted()))" : "") + - String(locked > 0 ? "\n🔒 Locked" : "") } + String(locked > 0 ? "\n🔒 Locked" : "") + } } struct WaypointCoordinate: Identifiable { - let id: UUID let coordinate: CLLocationCoordinate2D? let waypointId: Int64 diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index c1dc7023..0dd8ab06 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -53,6 +53,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let LEGACY_LOGRADIO_UUID = CBUUID(string: "0x6C6FD238-78FA-436B-AACF-15C5BE1EF2E2") let LOGRADIO_UUID = CBUUID(string: "0x5a3d6e49-06e6-4423-9944-e9de8cdf9547") + let NONCE_ONLY_CONFIG = 69420 + let NONCE_ONLY_DB = 69421 + private var isWaitingForWantConfigResponse = false + + private var wantConfigTimer: Timer? + private var wantConfigRetryCount = 0 + private let maxWantConfigRetries = 3 + private let wantConfigTimeoutInterval: TimeInterval = 10.0 + // MARK: init private override init() { // Default initialization should not be used @@ -184,6 +193,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if self.mqttProxyConnected { self.mqttManager.mqttClientProxy?.disconnect() } + self.wantConfigTimer?.invalidate() self.automaticallyReconnect = reconnect self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) self.FROMRADIO_characteristic = nil @@ -245,6 +255,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate // Disconnect Peripheral Event func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + resetWantConfigRetries() self.connectedPeripheral = nil self.isConnecting = false self.isConnected = false @@ -408,7 +419,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } // MARK: Protobuf Methods - func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32, context: NSManagedObjectContext) -> Int64 { + func requestDeviceMetadata(fromUser: UserEntity, toUser: UserEntity, context: NSManagedObjectContext) -> Int64 { guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return 0 } @@ -419,7 +430,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) meshPacket.priority = MeshPacket.Priority.reliable - meshPacket.channel = UInt32(adminIndex) meshPacket.wantAck = true var dataMessage = DataMessage() if let serializedData: Data = try? adminPacket.serializedData() { @@ -496,32 +506,96 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return success } + + func sendWantConfig() { + isWaitingForWantConfigResponse = true + + guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return } + + if FROMRADIO_characteristic == nil { + Logger.mesh.error("🚨 \("Unsupported Firmware Version Detected, unable to connect to device.".localized, privacy: .public)") + invalidVersion = true + return + } else { + + let nodeName = connectedPeripheral?.peripheral.name ?? "Unknown".localized + let logString = String.localizedStringWithFormat("Issuing Want Config to %@".localized, nodeName) + Logger.mesh.info("🛎️ \(logString, privacy: .public)") + + // BLE Characteristics discovered, issue wantConfig + var toRadio: ToRadio = ToRadio() + configNonce = UInt32(NONCE_ONLY_DB) + if !isSubscribed { + configNonce = UInt32(NONCE_ONLY_CONFIG) // Get config first + } + toRadio.wantConfigID = configNonce + guard let binaryData: Data = try? toRadio.serializedData() else { + return + } + connectedPeripheral!.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) + + // Either Read the config complete value or from num notify value + guard connectedPeripheral != nil else { return } + connectedPeripheral!.peripheral.readValue(for: FROMRADIO_characteristic) + + // Start timeout timer + startWantConfigTimeout() + } + } - func sendWantConfig() { - guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return } + private func startWantConfigTimeout() { + // Cancel any existing timer + wantConfigTimer?.invalidate() + + // Start new timer + wantConfigTimer = Timer.scheduledTimer(withTimeInterval: wantConfigTimeoutInterval, repeats: false) { [weak self] _ in + self?.handleWantConfigTimeout() + } + } - if FROMRADIO_characteristic == nil { - Logger.mesh.error("🚨 \("Unsupported Firmware Version Detected, unable to connect to device.".localized, privacy: .public)") - invalidVersion = true - return - } else { + private func handleWantConfigTimeout() { + guard isWaitingForWantConfigResponse else { return } + + wantConfigRetryCount += 1 + + if wantConfigRetryCount < maxWantConfigRetries { + Logger.mesh.warning("⏰ Want Config timeout, retrying... (attempt \(self.wantConfigRetryCount + 1)/\(self.maxWantConfigRetries))") + sendWantConfig() + } else { + Logger.mesh.error("🚨 Want Config failed after \(self.maxWantConfigRetries) attempts, forcing disconnect") + forceDisconnect() + } + } - let nodeName = connectedPeripheral?.peripheral.name ?? "Unknown".localized - let logString = String.localizedStringWithFormat("Issuing Want Config to %@".localized, nodeName) - Logger.mesh.info("🛎️ \(logString, privacy: .public)") - // BLE Characteristics discovered, issue wantConfig - var toRadio: ToRadio = ToRadio() - configNonce += 1 - toRadio.wantConfigID = configNonce - guard let binaryData: Data = try? toRadio.serializedData() else { - return - } - connectedPeripheral!.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - // Either Read the config complete value or from num notify value - guard connectedPeripheral != nil else { return } - connectedPeripheral!.peripheral.readValue(for: FROMRADIO_characteristic) - } - } + func onWantConfigResponseReceived() { + if isWaitingForWantConfigResponse { + isWaitingForWantConfigResponse = false + wantConfigTimer?.invalidate() + wantConfigTimer = nil + wantConfigRetryCount = 0 // Reset retry count on success + } + } + + private func forceDisconnect() { + isWaitingForWantConfigResponse = false + wantConfigTimer?.invalidate() + wantConfigTimer = nil + wantConfigRetryCount = 0 + + disconnectPeripheral(reconnect: false) + + lastConnectionError = "Bluetooth connection timeout, keep your node closer.".localized + + Logger.mesh.error("💥 [BLE] Forced disconnect due to Want Config timeout") + } + + // Call this to reset the retry mechanism (e.g., on new connection) + func resetWantConfigRetries() { + wantConfigRetryCount = 0 + wantConfigTimer?.invalidate() + wantConfigTimer = nil + isWaitingForWantConfigResponse = false + } func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { if let error { @@ -640,6 +714,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let message = CocoaMQTTMessage(topic: decodedInfo.mqttClientProxyMessage.topic, payload: [UInt8](decodedInfo.mqttClientProxyMessage.data), retained: decodedInfo.mqttClientProxyMessage.retained) mqttManager.mqttClientProxy?.publish(message) } else if decodedInfo.payloadVariant == FromRadio.OneOf_PayloadVariant.clientNotification(decodedInfo.clientNotification) { + + var path = "meshtastic:///settings/debugLogs" if decodedInfo.clientNotification.hasReplyID { /// Set Sent bool on TraceRouteEntity to false if we got rate limited if decodedInfo.clientNotification.message.starts(with: "TraceRoute") { @@ -653,6 +729,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let nsError = error as NSError Logger.data.error("💥 [TraceRouteEntity] Error Updating Core Data: \(nsError, privacy: .public)") } + } else if decodedInfo.clientNotification.message.starts(with: "You Device is configured with a low entropy") || decodedInfo.clientNotification.message.starts(with: "Compromised keys detected") { + path = "meshtastic:///settings/security" } } let manager = LocalNotificationManager() @@ -663,7 +741,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate subtitle: "\(decodedInfo.clientNotification.level)".capitalized, content: decodedInfo.clientNotification.message, target: "settings", - path: "meshtastic:///settings/debugLogs" + path: path ) ] manager.schedule() @@ -694,6 +772,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } // NodeInfo if decodedInfo.nodeInfo.num > 0 { + onWantConfigResponseReceived() nowKnown = true if let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: context) { if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo.num { @@ -716,6 +795,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } // Module Config if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0 { + onWantConfigResponseReceived() nowKnown = true moduleConfig(config: decodedInfo.moduleConfig, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName) if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) { @@ -982,7 +1062,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.mesh.warning("🕸️ MESH PACKET received for Key Verification App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") } - if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce { + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == NONCE_ONLY_CONFIG { invalidVersion = false lastConnectionError = "" isSubscribed = true @@ -1022,7 +1102,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } catch { Logger.data.error("Failed to find a node info for the connected node \(error.localizedDescription, privacy: .public)") } + Logger.mesh.info("🤜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)") + sendWantConfig() + } + // MARK: Share Location Position Update Timer // Use context to pass the radio name with the timer @@ -1036,6 +1120,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return } + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == NONCE_ONLY_DB { + Logger.mesh.info("🤜 [BLE] Want Config DB Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)") + } case FROMNUM_UUID: Logger.services.info("🗞️ [BLE] (Notify) characteristic value will be read next") @@ -1185,7 +1272,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } public func sendWaypoint(waypoint: Waypoint) -> Bool { - if waypoint.latitudeI == 373346000 && waypoint.longitudeI == -1220090000 { + if waypoint.latitudeI == 0 && waypoint.longitudeI == 0 { return false } var success = false @@ -1419,7 +1506,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return false } - public func sendShutdown(fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Bool { + public func sendShutdown(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.shutdownSeconds = 5 if fromUser != toUser { @@ -1431,7 +1518,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func sendReboot(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootSeconds = 5 if fromUser != toUser { @@ -1459,7 +1545,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func sendRebootOta(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootOtaSeconds = 5 if fromUser != toUser { @@ -1487,7 +1572,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setOwner = config if fromUser != toUser { @@ -1852,7 +1936,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveLicensedUser(ham: HamParameters, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setHamMode = ham if fromUser != toUser { @@ -2035,7 +2118,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveBluetoothConfig(config: Config.BluetoothConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.bluetooth = config if fromUser != toUser { @@ -2061,7 +2143,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveDeviceConfig(config: Config.DeviceConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.device = config @@ -2092,7 +2173,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveDisplayConfig(config: Config.DisplayConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.display = config if fromUser != toUser { @@ -2120,9 +2200,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) meshPacket.from = UInt32(fromUser.num) - if adminIndex > 0 { - meshPacket.channel = UInt32(adminIndex) - } meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveLoRaConfig(config: Config.LoRaConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.lora = config @@ -2151,7 +2228,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func savePositionConfig(config: Config.PositionConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.position = config @@ -2181,7 +2257,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func savePowerConfig(config: Config.PowerConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.power = config @@ -2212,7 +2287,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveNetworkConfig(config: Config.NetworkConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.network = config @@ -2245,7 +2319,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveSecurityConfig(config: Config.SecurityConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setConfig.security = config @@ -2278,7 +2351,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveAmbientLightingModuleConfig(config: ModuleConfig.AmbientLightingConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.ambientLighting = config @@ -2311,7 +2383,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveCannedMessageModuleConfig(config: ModuleConfig.CannedMessageConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.cannedMessage = config @@ -2343,7 +2414,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveCannedMessageModuleMessages(messages: String, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setCannedMessageModuleMessages = messages @@ -2375,7 +2445,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.detectionSensor = config @@ -2409,7 +2478,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveExternalNotificationModuleConfig(config: ModuleConfig.ExternalNotificationConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.externalNotification = config @@ -2439,7 +2507,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func savePaxcounterModuleConfig(config: ModuleConfig.PaxcounterConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.paxcounter = config @@ -2470,7 +2537,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate 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 { + public func saveRtttlConfig(ringtone: String, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setRingtoneMessage = ringtone @@ -2503,7 +2569,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveMQTTConfig(config: ModuleConfig.MQTTConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.mqtt = config @@ -2535,7 +2600,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveRangeTestModuleConfig(config: ModuleConfig.RangeTestConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.rangeTest = config @@ -2567,7 +2631,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveSerialModuleConfig(config: ModuleConfig.SerialConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.serial = config @@ -2599,7 +2662,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveStoreForwardModuleConfig(config: ModuleConfig.StoreForwardConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.storeForward = config @@ -2630,7 +2692,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + public func saveTelemetryModuleConfig(config: ModuleConfig.TelemetryConfig, fromUser: UserEntity, toUser: UserEntity) -> Int64 { var adminPacket = AdminMessage() adminPacket.setModuleConfig.telemetry = config @@ -2661,7 +2722,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestBluetoothConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.bluetoothConfig @@ -2766,7 +2826,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestDeviceConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.deviceConfig @@ -2796,7 +2855,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestDisplayConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.displayConfig @@ -2826,7 +2884,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestLoRaConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.loraConfig @@ -2856,7 +2913,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestNetworkConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.networkConfig @@ -2888,7 +2944,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestPositionConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.positionConfig @@ -2917,7 +2972,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestPowerConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.powerConfig @@ -2946,7 +3000,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestSecurityConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getConfigRequest = AdminMessage.ConfigType.securityConfig @@ -2975,7 +3028,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestAmbientLightingConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.ambientlightingConfig @@ -3004,7 +3056,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestCannedMessagesModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.cannedmsgConfig @@ -3033,7 +3084,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestExternalNotificationModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.extnotifConfig @@ -3062,7 +3112,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestPaxCounterModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.paxcounterConfig @@ -3091,7 +3140,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestRtttlConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getRingtoneRequest = true @@ -3120,7 +3168,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestRangeTestModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.rangetestConfig @@ -3149,7 +3196,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestMqttModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.mqttConfig @@ -3178,7 +3224,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestDetectionSensorModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.detectionsensorConfig @@ -3207,7 +3252,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestSerialModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.serialConfig @@ -3236,7 +3280,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestStoreAndForwardModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.storeforwardConfig @@ -3265,7 +3308,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func requestTelemetryModuleConfig(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.getModuleConfigRequest = AdminMessage.ModuleConfigType.telemetryConfig @@ -3295,7 +3337,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Constants.minimumNodeNum { - let newUser = createUser(num: Int64(nodeInfo.num), context: context) - newNode.user = newUser + do { + let newUser = try createUser(num: Int64(nodeInfo.num), context: context) + newNode.user = newUser + } catch CoreDataError.invalidInput(let message) { + Logger.data.error("Error Creating a new Core Data UserEntity (Invalid Input) from node number: \(nodeInfo.num, privacy: .public) Error: \(message, privacy: .public)") + } catch { + Logger.data.error("Error Creating a new Core Data UserEntity from node number: \(nodeInfo.num, privacy: .public) Error: \(error.localizedDescription, privacy: .public)") + } } if (nodeInfo.position.longitudeI != 0 && nodeInfo.position.latitudeI != 0) && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { @@ -420,9 +423,14 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } } else { if fetchedNode[0].user == nil && nodeInfo.num > Constants.minimumNodeNum { - - let newUser = createUser(num: Int64(nodeInfo.num), context: context) - fetchedNode[0].user = newUser + do { + let newUser = try createUser(num: Int64(nodeInfo.num), context: context) + fetchedNode[0].user = newUser + } catch CoreDataError.invalidInput(let message) { + Logger.data.error("Error Creating a new Core Data UserEntity on an existing node (Invalid Input) from node number: \(nodeInfo.num, privacy: .public) Error: \(message, privacy: .public)") + } catch { + Logger.data.error("Error Creating a new Core Data UserEntity on an existing node from node number: \(nodeInfo.num, privacy: .public) Error: \(error.localizedDescription, privacy: .public)") + } } } @@ -711,7 +719,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage if let telemetryMessage = try? Telemetry(serializedBytes: packet.decoded.payload) { let logString = String.localizedStringWithFormat("Telemetry received for: %@".localized, String(packet.from)) Logger.mesh.info("📈 \(logString, privacy: .public)") - if telemetryMessage.variant != Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) && telemetryMessage.variant != Telemetry.OneOf_Variant.powerMetrics(telemetryMessage.powerMetrics) { + if telemetryMessage.variant != Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) && telemetryMessage.variant != Telemetry.OneOf_Variant.localStats(telemetryMessage.localStats) && telemetryMessage.variant != Telemetry.OneOf_Variant.powerMetrics(telemetryMessage.powerMetrics) { /// Other unhandled telemetry packets return } @@ -932,7 +940,14 @@ func textMessageAppPacket( // For S&F broadcast messages, treat as a channel message (not a DM) newMessage.toUser = nil } else { - newMessage.toUser = createUser(num: Int64(truncatingIfNeeded: packet.to), context: context) + do { + let newUser = try createUser(num: Int64(truncatingIfNeeded: packet.to), context: context) + newMessage.toUser = newUser + } catch CoreDataError.invalidInput(let message) { + Logger.data.error("Error Creating a new Core Data UserEntity (Invalid Input) from node number: \(packet.to, privacy: .public) Error: \(message, privacy: .public)") + } catch { + Logger.data.error("Error Creating a new Core Data UserEntity from node number: \(packet.to, privacy: .public) Error: \(error.localizedDescription, privacy: .public)") + } } } if fetchedUsers.first(where: { $0.num == packet.from }) != nil { @@ -962,7 +977,14 @@ func textMessageAppPacket( } } else { /// Make a new from user if they are unknown - newMessage.fromUser = createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) + do { + let newUser = try createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) + newMessage.fromUser = newUser + } catch CoreDataError.invalidInput(let message) { + Logger.data.error("Error Creating a new Core Data UserEntity (Invalid Input) from node number: \(packet.from, privacy: .public) Error: \(message, privacy: .public)") + } catch { + Logger.data.error("Error Creating a new Core Data UserEntity from node number: \(packet.from, privacy: .public) Error: \(error.localizedDescription, privacy: .public)") + } } if packet.rxTime > 0 { newMessage.fromUser?.userNode?.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) @@ -979,79 +1001,79 @@ func textMessageAppPacket( try context.save() Logger.data.info("💾 Saved a new message for \(newMessage.messageId, privacy: .public)") messageSaved = true - - if messageSaved { - if packet.decoded.portnum == PortNum.detectionSensorApp && !UserDefaults.enableDetectionNotifications { - return - } - if newMessage.fromUser != nil && newMessage.toUser != nil { - // Set Unread Message Indicators - if packet.to == connectedNode { - appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0 - } - if !(newMessage.fromUser?.mute ?? false) { - // Create an iOS Notification for the received DM message - let manager = LocalNotificationManager() - manager.notifications = [ - Notification( - id: ("notification.id.\(newMessage.messageId)"), - title: "\(newMessage.fromUser?.longName ?? "Unknown".localized)", - subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", - content: messageText!, - target: "messages", - path: "meshtastic:///messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)", - messageId: newMessage.messageId, - channel: newMessage.channel, - userNum: Int64(packet.from), - critical: critical - ) - ] - manager.schedule() - Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown".localized, privacy: .public)") - } - } else if newMessage.fromUser != nil && newMessage.toUser == nil { - let fetchMyInfoRequest = MyInfoEntity.fetchRequest() - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode)) - do { - let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) - if !fetchedMyInfo.isEmpty { - appState.unreadChannelMessages = fetchedMyInfo[0].unreadMessages - for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { - if channel.index == newMessage.channel { - context.refresh(channel, mergeChanges: true) - } - if channel.index == newMessage.channel && !channel.mute && UserDefaults.channelMessageNotifications { - // Create an iOS Notification for the received channel message - let manager = LocalNotificationManager() - manager.notifications = [ - Notification( - id: ("notification.id.\(newMessage.messageId)"), - title: "\(newMessage.fromUser?.longName ?? "Unknown".localized)", - subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", - content: messageText!, - target: "messages", - path: "meshtastic:///messages?channelId=\(newMessage.channel)&messageId=\(newMessage.messageId)", - messageId: newMessage.messageId, - channel: newMessage.channel, - userNum: Int64(newMessage.fromUser?.userId ?? "0"), - critical: critical - ) - ] - manager.schedule() - Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown".localized, privacy: .public)") - } - } - } - } catch { - // Handle error - } - } - } } catch { context.rollback() let nsError = error as NSError Logger.data.error("Failed to save new MessageEntity \(nsError, privacy: .public)") } + // Send notifications if the message saved properly to core data + if messageSaved { + if packet.decoded.portnum == PortNum.detectionSensorApp && !UserDefaults.enableDetectionNotifications { + return + } + if newMessage.fromUser != nil && newMessage.toUser != nil { + // Set Unread Message Indicators + if packet.to == connectedNode { + appState.unreadDirectMessages = newMessage.toUser?.unreadMessages ?? 0 + } + if !(newMessage.fromUser?.mute ?? false) { + // Create an iOS Notification for the received DM message + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: ("notification.id.\(newMessage.messageId)"), + title: "\(newMessage.fromUser?.longName ?? "Unknown".localized)", + subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", + content: messageText!, + target: "messages", + path: "meshtastic:///messages?userNum=\(newMessage.fromUser?.num ?? 0)&messageId=\(newMessage.messageId)", + messageId: newMessage.messageId, + channel: newMessage.channel, + userNum: Int64(packet.from), + critical: critical + ) + ] + manager.schedule() + Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown".localized, privacy: .public)") + } + } else if newMessage.fromUser != nil && newMessage.toUser == nil { + let fetchMyInfoRequest = MyInfoEntity.fetchRequest() + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(connectedNode)) + do { + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) + if !fetchedMyInfo.isEmpty { + appState.unreadChannelMessages = fetchedMyInfo[0].unreadMessages + for channel in (fetchedMyInfo[0].channels?.array ?? []) as? [ChannelEntity] ?? [] { + if channel.index == newMessage.channel { + context.refresh(channel, mergeChanges: true) + } + if channel.index == newMessage.channel && !channel.mute && UserDefaults.channelMessageNotifications { + // Create an iOS Notification for the received channel message + let manager = LocalNotificationManager() + manager.notifications = [ + Notification( + id: ("notification.id.\(newMessage.messageId)"), + title: "\(newMessage.fromUser?.longName ?? "Unknown".localized)", + subtitle: "AKA \(newMessage.fromUser?.shortName ?? "?")", + content: messageText!, + target: "messages", + path: "meshtastic:///messages?channelId=\(newMessage.channel)&messageId=\(newMessage.messageId)", + messageId: newMessage.messageId, + channel: newMessage.channel, + userNum: Int64(newMessage.fromUser?.userId ?? "0"), + critical: critical + ) + ] + manager.schedule() + Logger.services.debug("iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown".localized, privacy: .public)") + } + } + } + } catch { + // Handle error + } + } + } } catch { Logger.data.error("Fetch Message To and From Users Error") } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 160ee4b2..057ab601 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV 52.xcdatamodel + MeshtasticDataModelV 53.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 53.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 53.xcdatamodel/contents new file mode 100644 index 00000000..c1ee4271 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV 53.xcdatamodel/contents @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index a286b08e..c241831a 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -170,8 +170,14 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) if newUserMessage.id.isEmpty { if packet.from > Constants.minimumNodeNum { - let newUser = createUser(num: Int64(packet.from), context: context) - newNode.user = newUser + do { + let newUser = try createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) + newNode.user = newUser + } catch CoreDataError.invalidInput(let message) { + Logger.data.error("Error Creating a new Core Data UserEntity (Invalid Input) from node number: \(packet.from, privacy: .public) Error: \(message, privacy: .public)") + } catch { + Logger.data.error("Error Creating a new Core Data UserEntity from node number: \(packet.from, privacy: .public) Error: \(error.localizedDescription, privacy: .public)") + } } } else { @@ -225,17 +231,34 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) } } else { if packet.from > Constants.minimumNodeNum { - let newUser = createUser(num: Int64(packet.from), context: context) - if !packet.publicKey.isEmpty { - newNode.user?.pkiEncrypted = true - newNode.user?.publicKey = packet.publicKey + do { + let newUser = try createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) + if !packet.publicKey.isEmpty { + newNode.user?.pkiEncrypted = true + newNode.user?.publicKey = packet.publicKey + } + newNode.user = newUser + } catch CoreDataError.invalidInput(let message) { + Logger.data.error("Error Creating a new Core Data UserEntity (Invalid Input) from node number: \(packet.from, privacy: .public) Error: \(message, privacy: .public)") + } catch { + Logger.data.error("Error Creating a new Core Data UserEntity from node number: \(packet.from, privacy: .public) Error: \(error.localizedDescription, privacy: .public)") } - newNode.user = newUser } } - + // User is messed up and has failed to create at least once, if this fails bail out if newNode.user == nil && packet.from > Constants.minimumNodeNum { - newNode.user = createUser(num: Int64(packet.from), context: context) + do { + let newUser = try createUser(num: Int64(packet.from), context: context) + newNode.user = newUser + } catch CoreDataError.invalidInput(let message) { + Logger.data.error("Error Creating a new Core Data UserEntity (Invalid Input) from node number: \(packet.from, privacy: .public) Error: \(message, privacy: .public)") + context.rollback() + return + } catch { + Logger.data.error("Error Creating a new Core Data UserEntity from node number: \(packet.from, privacy: .public) Error: \(error.localizedDescription, privacy: .public)") + context.rollback() + return + } } let myInfoEntity = MyInfoEntity(context: context) @@ -317,9 +340,14 @@ func upsertNodeInfoPacket (packet: MeshPacket, context: NSManagedObjectContext) fetchedNode[0].hopsAway = Int32(packet.hopStart - packet.hopLimit) } if fetchedNode[0].user == nil { - let newUser = createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) - fetchedNode[0].user? = newUser - + do { + let newUser = try createUser(num: Int64(truncatingIfNeeded: packet.from), context: context) + fetchedNode[0].user = newUser + } catch CoreDataError.invalidInput(let message) { + Logger.data.error("Error Creating a new Core Data UserEntity on an existing node (Invalid Input) from node number: \(packet.from, privacy: .public) Error: \(message, privacy: .public)") + } catch { + Logger.data.error("Error Creating a new Core Data UserEntity on an existing node from node number: \(packet.from, privacy: .public) Error: \(error.localizedDescription, privacy: .public)") + } } do { try context.save() @@ -553,6 +581,7 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses newDisplayConfig.displayMode = Int32(config.displaymode.rawValue) newDisplayConfig.units = Int32(config.units.rawValue) newDisplayConfig.headingBold = config.headingBold + newDisplayConfig.use12HClock = config.use12HClock fetchedNode[0].displayConfig = newDisplayConfig } else { @@ -564,6 +593,7 @@ func upsertDisplayConfigPacket(config: Config.DisplayConfig, nodeNum: Int64, ses fetchedNode[0].displayConfig?.oledType = Int32(config.oled.rawValue) fetchedNode[0].displayConfig?.displayMode = Int32(config.displaymode.rawValue) fetchedNode[0].displayConfig?.units = Int32(config.units.rawValue) + fetchedNode[0].displayConfig?.use12HClock = config.use12HClock fetchedNode[0].displayConfig?.headingBold = config.headingBold } if sessionPasskey != nil { diff --git a/Meshtastic/Resources/DeviceHardware.json b/Meshtastic/Resources/DeviceHardware.json index bbe99f2a..4163ebaa 100644 --- a/Meshtastic/Resources/DeviceHardware.json +++ b/Meshtastic/Resources/DeviceHardware.json @@ -229,7 +229,8 @@ "images": [ "tlora-t3s3-epaper.svg" ], - "requiresDfu": true + "requiresDfu": true, + "hasInkHud": true }, { "hwModel": 17, @@ -604,7 +605,7 @@ "hwModelSlug": "HELTEC_WIRELESS_TRACKER", "platformioTarget": "tracksenger", "architecture": "esp32-s3", - "activelySupported": false, + "activelySupported": true, "supportLevel": 3, "displayName": "TrackSenger (small TFT)", "requiresDfu": true, @@ -626,7 +627,7 @@ "hwModelSlug": "HELTEC_WIRELESS_TRACKER", "platformioTarget": "tracksenger-oled", "architecture": "esp32-s3", - "activelySupported": false, + "activelySupported": true, "supportLevel": 3, "displayName": "TrackSenger (big OLED)", "partitionScheme": "8MB" @@ -872,5 +873,110 @@ "images": [ "thinknode_m2.svg" ] + }, + { + "hwModel": 94, + "hwModelSlug": "HELTEC_MESH_POCKET", + "platformioTarget": "heltec-mesh-pocket-10000", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec MeshPocket", + "tags": [ + "Heltec" + ], + "images": [ + "heltec_mesh_pocket.svg" + ], + "requiresDfu": true, + "hasInkHud": true + }, + { + "hwModel": 95, + "hwModelSlug": "SEEED_SOLAR_NODE", + "platformioTarget": "seeed_solar_node", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Seeed SenseCAP Solar Node", + "tags": [ + "Seeed" + ], + "images": [ + "seeed_solar.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 99, + "hwModelSlug": "SEEED_WIO_TRACKER_L1", + "platformioTarget": "seeed_wio_tracker_L1", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Seeed Wio Tracker L1", + "tags": [ + "Seeed" + ], + "images": [ + "wio_tracker_l1_case.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 97, + "hwModelSlug": "CROWPANEL", + "platformioTarget": "elecrow-adv1-43-50-70-tft", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Crowpanel Adv 4.3/5.0/7.0 TFT", + "tags": [ + "Elecrow" + ], + "requiresDfu": true, + "images": [ + "crowpanel_5_0.svg", + "crowpanel_7_0.svg" + ], + "partitionScheme": "16MB", + "hasMui": true + }, + { + "hwModel": 97, + "hwModelSlug": "CROWPANEL", + "platformioTarget": "elecrow-adv-24-28-tft", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Crowpanel Adv 2.4/2.8 TFT", + "tags": [ + "Elecrow" + ], + "requiresDfu": true, + "images": [ + "crowpanel_2_4.svg", + "crowpanel_2_8.svg" + ], + "partitionScheme": "16MB", + "hasMui": true + }, + { + "hwModel": 97, + "hwModelSlug": "CROWPANEL", + "platformioTarget": "elecrow-adv-35-tft", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Crowpanel Adv 3.5 TFT", + "tags": [ + "Elecrow" + ], + "requiresDfu": true, + "images": [ + "crowpanel_3_5.svg" + ], + "partitionScheme": "16MB", + "hasMui": true } ] diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index a804a77d..4e114bb9 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -133,7 +133,7 @@ struct Connect: View { Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") } Button { - if !bleManager.sendShutdown(fromUser: node!.user!, toUser: node!.user!, adminIndex: node!.myInfo!.adminIndex) { + if !bleManager.sendShutdown(fromUser: node!.user!, toUser: node!.user!) { Logger.mesh.error("Shutdown Failed") } diff --git a/Meshtastic/Views/Messages/ChannelList.swift b/Meshtastic/Views/Messages/ChannelList.swift index 61f4fe00..fec3ab8b 100644 --- a/Meshtastic/Views/Messages/ChannelList.swift +++ b/Meshtastic/Views/Messages/ChannelList.swift @@ -58,6 +58,13 @@ struct ChannelList: View { VStack(alignment: .leading) { HStack { + if channel.psk?.hexDescription.count ?? 0 < 3 { + Image(systemName: "lock.slash.fill") + .foregroundColor(.red) + } else { + Image(systemName: "lock.fill") + .foregroundColor(.green) + } if channel.name?.isEmpty ?? false { if channel.role == 1 { Text(String("PrimaryChannel").camelCaseToWords()) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 9dd3b5cf..42bd5c12 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -180,24 +180,24 @@ struct ChannelMessageList: View { } .scrollDismissesKeyboard(.interactively) .onFirstAppear { - // Find first unread message - if let firstUnreadMessageId = channel.allPrivateMessages.first(where: { !$0.read })?.messageId { + if channel.unreadMessages == 0 { withAnimation { - scrollView.scrollTo(firstUnreadMessageId, anchor: .top) - showScrollToBottomButton = true + scrollView.scrollTo("bottomAnchor", anchor: .bottom) + hasReachedBottom = true } } else { - // If no unread messages, scroll to bottom - withAnimation { - scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) - hasReachedBottom = true + if let firstUnreadMessageId = channel.allPrivateMessages.first(where: { !$0.read })?.messageId { + withAnimation { + scrollView.scrollTo(firstUnreadMessageId, anchor: .top) + showScrollToBottomButton = true + } } } gotFirstUnreadMessage = true } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in withAnimation { - scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) + scrollView.scrollTo("bottomAnchor", anchor: .bottom) hasReachedBottom = true showScrollToBottomButton = false } @@ -205,7 +205,7 @@ struct ChannelMessageList: View { .onChange(of: channel.allPrivateMessages) { if hasReachedBottom { withAnimation { - scrollView.scrollTo(channel.allPrivateMessages.last?.messageId ?? 0, anchor: .bottom) + scrollView.scrollTo("bottomAnchor", anchor: .bottom) } } else { showScrollToBottomButton = true diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index e84686d6..7b27b4f2 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -167,24 +167,24 @@ struct UserMessageList: View { } .scrollDismissesKeyboard(.interactively) .onFirstAppear { - // Find first unread message - if let firstUnreadMessageId = user.messageList.first(where: { !$0.read })?.messageId { + if user.unreadMessages == 0 { withAnimation { - scrollView.scrollTo(firstUnreadMessageId, anchor: .top) - showScrollToBottomButton = true + scrollView.scrollTo("bottomAnchor", anchor: .bottom) + hasReachedBottom = true } } else { - // If no unread messages, scroll to bottom - withAnimation { - scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) - hasReachedBottom = true + if let firstUnreadMessageId = user.messageList.first(where: { !$0.read })?.messageId { + withAnimation { + scrollView.scrollTo(firstUnreadMessageId, anchor: .top) + showScrollToBottomButton = true + } } } gotFirstUnreadMessage = true } .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in withAnimation { - scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) + scrollView.scrollTo("bottomAnchor", anchor: .bottom) hasReachedBottom = true showScrollToBottomButton = false } @@ -192,7 +192,7 @@ struct UserMessageList: View { .onChange(of: user.messageList) { if hasReachedBottom { withAnimation { - scrollView.scrollTo(user.messageList.last?.messageId ?? 0, anchor: .bottom) + scrollView.scrollTo("bottomAnchor", anchor: .bottom) } } else { showScrollToBottomButton = true diff --git a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift index ad342cbc..5866e8ed 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/WaypointForm.swift @@ -30,6 +30,7 @@ struct WaypointForm: View { @State private var lockedTo: Int64 = 0 @State private var detents: Set = [.medium, .fraction(0.85)] @State private var selectedDetent: PresentationDetent = .medium + @State private var waypointFailedAlert: Bool = false var body: some View { NavigationStack { @@ -47,7 +48,19 @@ struct WaypointForm: View { .textSelection(.enabled) .foregroundColor(.secondary) .font(.caption) + } + Button { + let currentLoc = LocationsHandler.currentLocation + waypoint.coordinate.longitude = currentLoc.longitude + waypoint.coordinate.latitude = currentLoc.latitude + } label: { + HStack { + Text("Use my Location") + Image(systemName: "location") + } + } + .accessibilityLabel("Set to current location") HStack { if waypoint.coordinate.latitude != 0 && waypoint.coordinate.longitude != 0 { DistanceText(meters: distance) @@ -72,6 +85,7 @@ struct WaypointForm: View { name = String(name.dropLast()) totalBytes = name.utf8.count } + waypoint.name = name.count > 0 ? name : "Dropped Pin" } } HStack { @@ -167,8 +181,8 @@ struct WaypointForm: View { if bleManager.sendWaypoint(waypoint: newWaypoint) { dismiss() } else { - dismiss() Logger.mesh.warning("Send waypoint failed") + waypointFailedAlert = true } } else { Logger.mesh.warning("Send waypoint failed, node not connected") @@ -233,8 +247,8 @@ struct WaypointForm: View { } dismiss() } else { - dismiss() Logger.mesh.warning("Send waypoint failed") + waypointFailedAlert = true } }) } @@ -256,8 +270,8 @@ struct WaypointForm: View { Text(waypoint.name ?? "?") .font(.largeTitle) Spacer() - if waypoint.locked > 0 { - Image(systemName: "lock.fill" ) + if waypoint.locked > 0 && waypoint.locked != UInt32(BLEManager.shared.connectedPeripheral?.num ?? 0) { + Image(systemName: "lock.fill") .font(.largeTitle) } else { Button { @@ -368,6 +382,17 @@ struct WaypointForm: View { } } } + .alert("Waypoint Failed to Send", isPresented: $waypointFailedAlert) { + Button("OK", role: .cancel) { + bleManager.context.delete(waypoint) + do { + try bleManager.context.save() + } catch { + bleManager.context.rollback() + } + dismiss() + } + } .onDisappear { if waypoint.id == 0 { // New, unsent waypoint created by the user: delete it diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 27e6a86c..80d10839 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -516,7 +516,6 @@ struct NodeDetail: View { let adminMessageId = bleManager.requestDeviceMetadata( fromUser: connectedNode.user!, toUser: node.user!, - adminIndex: connectedNode.myInfo!.adminIndex, context: context ) if adminMessageId > 0 { @@ -543,8 +542,7 @@ struct NodeDetail: View { Button("Shutdown Node?", role: .destructive) { if !bleManager.sendShutdown( fromUser: connectedNode.user!, - toUser: node.user!, - adminIndex: connectedNode.myInfo!.adminIndex + toUser: node.user! ) { Logger.mesh.warning("Shutdown Failed") } @@ -566,8 +564,7 @@ struct NodeDetail: View { Button("Reboot node?", role: .destructive) { if !bleManager.sendReboot( fromUser: connectedNode.user!, - toUser: node.user!, - adminIndex: connectedNode.myInfo!.adminIndex + toUser: node.user! ) { Logger.mesh.warning("Reboot Failed") } diff --git a/Meshtastic/Views/Settings/About.swift b/Meshtastic/Views/Settings/About.swift index e65a4c64..98355864 100644 --- a/Meshtastic/Views/Settings/About.swift +++ b/Meshtastic/Views/Settings/About.swift @@ -38,7 +38,9 @@ struct AboutMeshtastic: View { } } } - Link("Help with App Development", destination: URL(string: "https://github.com/meshtastic/Meshtastic-Apple")!) + Link("Sponsor App Development", destination: URL(string: "https://github.com/sponsors/garthvh")!) + .font(.title2) + Link("GitHub Repository", destination: URL(string: "https://github.com/meshtastic/Meshtastic-Apple")!) .font(.title2) Button("Review the app") { if let scene = UIApplication.shared.connectedScenes diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 81b43499..be6b9522 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -80,7 +80,7 @@ struct BluetoothConfig: View { bc.enabled = enabled bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin bc.fixedPin = UInt32(fixedPin) ?? 123456 - let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: connectedNode.user!, toUser: node!.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: connectedNode.user!, toUser: node!.user!) 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 @@ -111,7 +111,7 @@ struct BluetoothConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.bluetoothConfig == nil { Logger.mesh.info("⚙️ Empty or expired bluetooth config requesting via PKI admin") - _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestBluetoothConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index bb9b6916..834dcc0f 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -245,7 +245,7 @@ struct DeviceConfig: View { dc.disableTripleClick = !tripleClickAsAdHocPing dc.tzdef = tzdef dc.ledHeartbeatDisabled = !ledHeartbeatEnabled - let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -278,7 +278,7 @@ struct DeviceConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.deviceConfig == nil { Logger.mesh.info("⚙️ Empty or expired device config requesting via PKI admin") - _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestDeviceConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { if node.deviceConfig == nil { diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index c9029408..682bfd45 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -27,6 +27,7 @@ struct DisplayConfig: View { @State var oledType = 0 @State var displayMode = 0 @State var units = 0 + @State var use12HourClock = false var body: some View { Form { @@ -74,6 +75,11 @@ struct DisplayConfig: View { .font(.callout) } .pickerStyle(DefaultPickerStyle()) + Toggle(isOn: $use12HourClock) { + Label("12 Hour Clock", systemImage: "clock") + Text("Sets the screen clock format to 12-hour.") + } + .tint(Color.accentColor) } Section(header: Text("Timing & Format")) { VStack(alignment: .leading) { @@ -141,8 +147,9 @@ struct DisplayConfig: View { dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue() dc.displaymode = DisplayModes(rawValue: displayMode)!.protoEnumValue() dc.units = Units(rawValue: units)!.protoEnumValue() + dc.use12HClock = use12HourClock - let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: connectedNode!.user!, toUser: node!.user!) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true @@ -174,7 +181,7 @@ struct DisplayConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.displayConfig == nil { Logger.mesh.info("⚙️ Empty or expired display config requesting via PKI admin") - _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestDisplayConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration @@ -211,6 +218,9 @@ struct DisplayConfig: View { .onChange(of: units) { oldUnits, newUnits in if oldUnits != newUnits && newUnits != node?.displayConfig?.units ?? -1 { hasChanges = true } } + .onChange(of: use12HourClock) { oldUse12HourClock, newUse12HourClock in + if oldUse12HourClock != newUse12HourClock && newUse12HourClock != node?.displayConfig?.use12HClock { hasChanges = true } + } } func setDisplayValues() { self.gpsFormat = Int(node?.displayConfig?.gpsFormat ?? 0) @@ -222,6 +232,7 @@ struct DisplayConfig: View { self.oledType = Int(node?.displayConfig?.oledType ?? 0) self.displayMode = Int(node?.displayConfig?.displayMode ?? 0) self.units = Int(node?.displayConfig?.units ?? 0) - self.hasChanges = false + self.use12HourClock = node?.displayConfig?.use12HClock ?? false + self.hasChanges = node?.displayConfig?.use12HClock ?? false } } diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 452da960..3e8beb9a 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -218,7 +218,7 @@ struct LoRaConfig: View { if connectedNode?.num ?? -1 == node?.user?.num ?? 0 { UserDefaults.modemPreset = modemPreset } - let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -250,7 +250,7 @@ struct LoRaConfig: View { if expiration < Date() || node.loRaConfig == nil { Logger.mesh.info("⚙️ Empty or expired lora config requesting via PKI admin") if connectedNode.user != nil && node.user != nil { - _ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestLoRaConfig(fromUser: connectedNode.user!, toUser: node.user!) } } } else { diff --git a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift index efbeed70..ad3e5e3c 100644 --- a/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/AmbientLightingConfig.swift @@ -66,7 +66,7 @@ struct AmbientLightingConfig: View { al.blue = UInt32(components.blue * 255) } - let adminMessageId = bleManager.saveAmbientLightingModuleConfig(config: al, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveAmbientLightingModuleConfig(config: al, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -96,7 +96,7 @@ struct AmbientLightingConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.ambientLightingConfig == nil { Logger.mesh.info("⚙️ Empty or expired ambient lighting module config requesting via PKI admin") - _ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestAmbientLightingConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 0fbcbcf8..941ed3fd 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -201,7 +201,7 @@ struct CannedMessagesConfig: View { cmc.inputbrokerEventCw = InputEventChars(rawValue: inputbrokerEventCw)!.protoEnumValue() cmc.inputbrokerEventCcw = InputEventChars(rawValue: inputbrokerEventCcw)!.protoEnumValue() cmc.inputbrokerEventPress = InputEventChars(rawValue: inputbrokerEventPress)!.protoEnumValue() - let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, fromUser: node!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, fromUser: node!.user!, toUser: node!.user!) 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 @@ -211,7 +211,7 @@ struct CannedMessagesConfig: View { } } if hasMessagesChanges { - let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messages, fromUser: node!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveCannedMessageModuleMessages(messages: messages, fromUser: node!.user!, toUser: node!.user!) 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 @@ -244,7 +244,7 @@ struct CannedMessagesConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.cannedMessageConfig == nil { Logger.mesh.info("⚙️ Empty or expired canned messages module config requesting via PKI admin") - _ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestCannedMessagesModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift index 94b96b5d..9d4b61a4 100644 --- a/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/DetectionSensorConfig.swift @@ -172,7 +172,7 @@ struct DetectionSensorConfig: View { dsc.usePullup = self.usePullup dsc.minimumBroadcastSecs = UInt32(self.minimumBroadcastSecs) dsc.stateBroadcastSecs = UInt32(self.stateBroadcastSecs) - let adminMessageId = bleManager.saveDetectionSensorModuleConfig(config: dsc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveDetectionSensorModuleConfig(config: dsc, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -202,7 +202,7 @@ struct DetectionSensorConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.detectionSensorConfig == nil { Logger.mesh.info("⚙️ Empty or expired detection sensor module config requesting via PKI admin") - _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 08745f04..d0c0b13b 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -180,7 +180,7 @@ struct ExternalNotificationConfig: View { enc.outputMs = UInt32(outputMilliseconds) enc.usePwm = usePWM enc.useI2SAsBuzzer = useI2SAsBuzzer - let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -210,7 +210,7 @@ struct ExternalNotificationConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.externalNotificationConfig == nil { Logger.mesh.info("⚙️ Empty or expired external notificaiton module config requesting via PKI admin") - _ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestExternalNotificationModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index f06cf45c..1e9ed3da 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -268,7 +268,7 @@ struct MQTTConfig: View { mqtt.mapReportingEnabled = self.mapReportingEnabled mqtt.mapReportSettings.positionPrecision = UInt32(self.mapPositionPrecision) mqtt.mapReportSettings.publishIntervalSecs = UInt32(self.mapPublishIntervalSecs) - let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -360,7 +360,7 @@ struct MQTTConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.mqttConfig == nil { Logger.mesh.info("⚙️ Empty or expired mqtt module config requesting via PKI admin") - _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestMqttModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift index 7c84b406..b0101f2b 100644 --- a/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/PaxCounterConfig.swift @@ -69,7 +69,7 @@ struct PaxCounterConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.paxCounterConfig == nil { Logger.mesh.info("⚙️ Empty or expired pax counter module config requesting via PKI admin") - _ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestPaxCounterModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration @@ -100,8 +100,7 @@ struct PaxCounterConfig: View { let adminMessageId = bleManager.savePaxcounterModuleConfig( config: config, fromUser: fromUser, - toUser: toUser, - adminIndex: connectedNode.myInfo?.adminIndex ?? 0 + toUser: toUser ) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index b2636967..a9b07c53 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -62,7 +62,7 @@ struct RangeTestConfig: View { rtc.enabled = enabled rtc.save = save rtc.sender = UInt32(sender) - let adminMessageId = bleManager.saveRangeTestModuleConfig(config: rtc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveRangeTestModuleConfig(config: rtc, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -92,7 +92,7 @@ struct RangeTestConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.rangeTestConfig == nil { Logger.mesh.info("⚙️ Empty or expired range test module config requesting via PKI admin") - _ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestRangeTestModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift index da30e1e4..ab1663a4 100644 --- a/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RtttlConfig.swift @@ -53,7 +53,7 @@ struct RtttlConfig: View { SaveConfigButton(node: node, hasChanges: $hasChanges) { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) if connectedNode != nil { - let adminMessageId = bleManager.saveRtttlConfig(ringtone: ringtone.trimmingCharacters(in: .whitespacesAndNewlines), fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveRtttlConfig(ringtone: ringtone.trimmingCharacters(in: .whitespacesAndNewlines), fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -83,7 +83,7 @@ struct RtttlConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.rtttlConfig == nil { Logger.mesh.info("⚙️ Empty or expired ringtone module config requesting via PKI admin") - _ = bleManager.requestRtttlConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestRtttlConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index d1aca540..daa4c21e 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -116,7 +116,7 @@ struct SerialConfig: View { sc.overrideConsoleSerialPort = overrideConsoleSerialPort sc.mode = SerialModeTypes(rawValue: mode)!.protoEnumValue() - let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: connectedNode!.user!, toUser: node!.user!) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true @@ -147,7 +147,7 @@ struct SerialConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.serialConfig == nil { Logger.mesh.info("⚙️ Empty or expired serial module config requesting via PKI admin") - _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestSerialModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift index c49fadf1..6f30ee33 100644 --- a/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/StoreForwardConfig.swift @@ -118,7 +118,7 @@ struct StoreForwardConfig: View { sfc.records = UInt32(self.records) sfc.historyReturnMax = UInt32(self.historyReturnMax) sfc.historyReturnWindow = UInt32(self.historyReturnWindow) - let adminMessageId = bleManager.saveStoreForwardModuleConfig(config: sfc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveStoreForwardModuleConfig(config: sfc, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -148,7 +148,7 @@ struct StoreForwardConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.storeForwardConfig == nil { Logger.mesh.info("⚙️ Empty or expired store & forward module config requesting via PKI admin") - _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestStoreAndForwardModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 0ee48f86..f87e7890 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -115,7 +115,7 @@ struct TelemetryConfig: View { tc.powerMeasurementEnabled = powerMeasurementEnabled tc.powerUpdateInterval = UInt32(powerUpdateInterval) tc.powerScreenEnabled = powerScreenEnabled - let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -145,7 +145,7 @@ struct TelemetryConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.telemetryConfig == nil { Logger.mesh.info("⚙️ Empty or expired telemetry module config requesting via PKI admin") - _ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestTelemetryModuleConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 57270f65..9d92ca03 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -114,7 +114,7 @@ struct NetworkConfig: View { network.enabledProtocols = self.udpEnabled ? UInt32(Config.NetworkConfig.ProtocolFlags.udpBroadcast.rawValue) : UInt32(Config.NetworkConfig.ProtocolFlags.noBroadcast.rawValue) // network.addressMode = Config.NetworkConfig.AddressMode.dhcp - let adminMessageId = bleManager.saveNetworkConfig(config: network, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveNetworkConfig(config: network, fromUser: connectedNode!.user!, toUser: node!.user!) 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 @@ -140,7 +140,7 @@ struct NetworkConfig: View { Logger.mesh.info("empty network config") let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) if node != nil && connectedNode != nil { - _ = bleManager.requestNetworkConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + _ = bleManager.requestNetworkConfig(fromUser: connectedNode!.user!, toUser: node!.user!) } } } @@ -155,7 +155,7 @@ struct NetworkConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.networkConfig == nil { Logger.mesh.info("⚙️ Empty or expired network config requesting via PKI admin") - _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestNetworkConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 3af2546d..537c1f26 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -345,7 +345,7 @@ struct PositionConfig: View { if includeSpeed { pf.insert(.Speed) } if includeHeading { pf.insert(.Heading) } pc.positionFlags = UInt32(pf.rawValue) - let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: connectedNode!.user!, toUser: node!.user!) if adminMessageId > 0 { // Disable the button after a successful save hasChanges = false @@ -412,7 +412,7 @@ struct PositionConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.positionConfig == nil { Logger.mesh.info("⚙️ Empty or expired position config requesting via PKI admin") - _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestPositionConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration diff --git a/Meshtastic/Views/Settings/Config/PowerConfig.swift b/Meshtastic/Views/Settings/Config/PowerConfig.swift index 9ba385ff..6087fec6 100644 --- a/Meshtastic/Views/Settings/Config/PowerConfig.swift +++ b/Meshtastic/Views/Settings/Config/PowerConfig.swift @@ -139,7 +139,7 @@ struct PowerConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.powerConfig == nil { Logger.mesh.info("⚙️ Empty or expired power config requesting via PKI admin") - _ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestPowerConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { /// Legacy Administration @@ -194,8 +194,7 @@ struct PowerConfig: View { let adminMessageId = bleManager.savePowerConfig( config: config, fromUser: fromUser, - toUser: toUser, - adminIndex: connectedNode.myInfo?.adminIndex ?? 0 + toUser: toUser ) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true diff --git a/Meshtastic/Views/Settings/Config/SecurityConfig.swift b/Meshtastic/Views/Settings/Config/SecurityConfig.swift index 13f40f98..2095ae9b 100644 --- a/Meshtastic/Views/Settings/Config/SecurityConfig.swift +++ b/Meshtastic/Views/Settings/Config/SecurityConfig.swift @@ -33,7 +33,6 @@ struct SecurityConfig: View { @State var isManaged = false @State var serialEnabled = false @State var debugLogApiEnabled = false - @State var adminChannelEnabled = false var body: some View { VStack { @@ -65,6 +64,21 @@ struct SecurityConfig: View { Text("Used to create a shared key with a remote device.") .foregroundStyle(.secondary) .font(idiom == .phone ? .caption : .callout) + HStack(alignment: .firstTextBaseline) { + Label("Regenerate Private Key", systemImage: "arrow.clockwise.circle") + Spacer() + Button { + if let keyBytes = generatePrivateKey(count: 32) { + privateKey = keyBytes.base64EncodedString() + } + } label: { + Image(systemName: "lock.rotation") + .font(.title) + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.small) + } Divider() Label("Primary Admin Key", systemImage: "key.viewfinder") SecureInput("Primary Admin Key", text: $adminKey, isValid: $hasValidAdminKey) @@ -109,19 +123,14 @@ struct SecurityConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } - Section(header: Text("Administration")) { - if adminKey.length > 0 || adminChannelEnabled { + if adminKey.length > 0 || UserDefaults.enableAdministration { + Section(header: Text("Administration")) { Toggle(isOn: $isManaged) { Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath") Text("Device is managed by a mesh administrator, the user is unable to access any of the device settings.") } .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)) } } } @@ -143,17 +152,14 @@ struct SecurityConfig: View { .onChange(of: debugLogApiEnabled) { _, newDebugLogApiEnabled in if newDebugLogApiEnabled != node?.securityConfig?.debugLogApiEnabled { hasChanges = true } } - .onChange(of: adminChannelEnabled) { _, newAdminChannelEnabled in - if newAdminChannelEnabled != node?.securityConfig?.adminChannelEnabled { hasChanges = true } - } - .onChange(of: privateKey) { + .onChange(of: privateKey) { _, key in let tempKey = Data(base64Encoded: privateKey) ?? Data() if tempKey.count == 32 { hasValidPrivateKey = true } else { hasValidPrivateKey = false } - hasChanges = true + if key != node?.securityConfig?.privateKey?.base64EncodedString() ?? "" && hasValidPrivateKey { hasChanges = true } } .onChange(of: adminKey) { _, key in let tempKey = Data(base64Encoded: key) ?? Data() @@ -164,7 +170,7 @@ struct SecurityConfig: View { } else { hasValidAdminKey = false } - hasChanges = true + if key != node?.securityConfig?.adminKey?.base64EncodedString() ?? "" && hasValidAdminKey { hasChanges = true } } .onChange(of: adminKey2) { _, key in let tempKey = Data(base64Encoded: key) ?? Data() @@ -175,7 +181,7 @@ struct SecurityConfig: View { } else { hasValidAdminKey2 = false } - hasChanges = true + if key != node?.securityConfig?.adminKey2?.base64EncodedString() ?? "" && hasValidAdminKey2 { hasChanges = true } } .onChange(of: adminKey3) { _, key in let tempKey = Data(base64Encoded: key) ?? Data() @@ -186,10 +192,10 @@ struct SecurityConfig: View { } else { hasValidAdminKey3 = false } - hasChanges = true + if key != node?.securityConfig?.adminKey3?.base64EncodedString() ?? "" && hasValidAdminKey3 { hasChanges = true } } .onFirstAppear { - // Need to request a DeviceConfig from the remote node before allowing changes + // Need to request a SecurityConfig from the remote node before allowing changes if let connectedPeripheral = bleManager.connectedPeripheral, let node { let connectedNode = getNodeInfo(id: connectedPeripheral.num, context: context) if let connectedNode { @@ -199,7 +205,7 @@ struct SecurityConfig: View { let expiration = node.sessionExpiration ?? Date() if expiration < Date() || node.securityConfig == nil { Logger.mesh.info("⚙️ Empty or expired security config requesting via PKI admin") - _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!, adminIndex: connectedNode.myInfo?.adminIndex ?? 0) + _ = bleManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!) } } else { if node.deviceConfig == nil { @@ -231,18 +237,26 @@ struct SecurityConfig: View { config.isManaged = isManaged config.serialEnabled = serialEnabled config.debugLogApiEnabled = debugLogApiEnabled - config.adminChannelEnabled = adminChannelEnabled + + let reboot = node?.securityConfig?.privateKey?.base64EncodedString() ?? "" != privateKey let adminMessageId = bleManager.saveSecurityConfig( config: config, fromUser: fromUser, - toUser: toUser, - adminIndex: connectedNode.myInfo?.adminIndex ?? 0 + toUser: toUser ) 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 + if reboot { + if !bleManager.sendReboot( + fromUser: fromUser, + toUser: toUser + ) { + Logger.mesh.warning("Reboot Failed") + } + } goBack() } } @@ -257,7 +271,24 @@ struct SecurityConfig: View { self.isManaged = node?.securityConfig?.isManaged ?? false self.serialEnabled = node?.securityConfig?.serialEnabled ?? false self.debugLogApiEnabled = node?.securityConfig?.debugLogApiEnabled ?? false - self.adminChannelEnabled = node?.securityConfig?.adminChannelEnabled ?? false self.hasChanges = false } + + func generatePrivateKey(count: Int) -> Data? { + var randomBytes = Data(count: count) + let status = randomBytes.withUnsafeMutableBytes { (mutableBytes: UnsafeMutableRawBufferPointer) -> Int32 in + guard let pointer = mutableBytes.baseAddress?.assumingMemoryBound(to: UInt8.self) else { + return -1 // Indicate an error + } + return SecRandomCopyBytes(kSecRandomDefault, count, pointer) + } + + if status == errSecSuccess { + return randomBytes + } else { + // Handle error, perhaps by logging or throwing an exception + print("Error generating random bytes: \(status)") + return nil + } + } } diff --git a/Meshtastic/Views/Settings/Firmware.swift b/Meshtastic/Views/Settings/Firmware.swift index 2380b677..f8225e73 100644 --- a/Meshtastic/Views/Settings/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware.swift @@ -160,7 +160,7 @@ struct Firmware: View { Button { let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? 0, context: context) if connectedNode != nil { - if !bleManager.sendRebootOta(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex) { + if !bleManager.sendRebootOta(fromUser: connectedNode!.user!, toUser: node!.user!) { Logger.mesh.error("Reboot Failed") } } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 2dec1530..a4b664b9 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -432,7 +432,7 @@ struct Settings: View { 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 { - let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) + let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, context: context) if adminMessageId > 0 { Logger.mesh.info("Sent node metadata request from node details") } diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index fa0e6370..d1c09deb 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -83,7 +83,7 @@ struct ShareChannels: View { .labelsHidden() Text(((channel.name!.isEmpty ? "Primary" : channel.name) ?? "Primary").camelCaseToWords()) if channel.psk?.hexDescription.count ?? 0 < 3 { - Image(systemName: "lock.slash") + Image(systemName: "lock.slash.fill") .foregroundColor(.red) } else { Image(systemName: "lock.fill") @@ -96,7 +96,7 @@ struct ShareChannels: View { .disabled(channel.role == 1) Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize() if channel.psk?.hexDescription.count ?? 0 < 3 { - Image(systemName: "lock.slash") + Image(systemName: "lock.slash.fill") .foregroundColor(.red) } else { Image(systemName: "lock.fill") @@ -109,7 +109,7 @@ struct ShareChannels: View { .disabled(channel.role == 1) Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize() if channel.psk?.hexDescription.count ?? 0 < 3 { - Image(systemName: "lock.slash") + Image(systemName: "lock.slash.fill") .foregroundColor(.red) } else { Image(systemName: "lock.fill") @@ -122,7 +122,7 @@ struct ShareChannels: View { .disabled(channel.role == 1) Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize() if channel.psk?.hexDescription.count ?? 0 < 3 { - Image(systemName: "lock.slash") + Image(systemName: "lock.slash.fill") .foregroundColor(.red) } else { Image(systemName: "lock.fill") @@ -135,7 +135,7 @@ struct ShareChannels: View { .disabled(channel.role == 1) Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize() if channel.psk?.hexDescription.count ?? 0 < 3 { - Image(systemName: "lock.slash") + Image(systemName: "lock.slash.fill") .foregroundColor(.red) } else { Image(systemName: "lock.fill") @@ -148,7 +148,7 @@ struct ShareChannels: View { .disabled(channel.role == 1) Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize() if channel.psk?.hexDescription.count ?? 0 < 3 { - Image(systemName: "lock.slash") + Image(systemName: "lock.slash.fill") .foregroundColor(.red) } else { Image(systemName: "lock.fill") @@ -161,7 +161,7 @@ struct ShareChannels: View { .disabled(channel.role == 1) Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize() if channel.psk?.hexDescription.count ?? 0 < 3 { - Image(systemName: "lock.slash") + Image(systemName: "lock.slash.fill") .foregroundColor(.red) } else { Image(systemName: "lock.fill") @@ -174,7 +174,7 @@ struct ShareChannels: View { .disabled(channel.role == 1) Text(((channel.name!.isEmpty ? "Channel\(channel.index)" : channel.name) ?? "Channel\(channel.index)").camelCaseToWords()).fixedSize() if channel.psk?.hexDescription.count ?? 0 < 3 { - Image(systemName: "lock.slash") + Image(systemName: "lock.slash.fill") .foregroundColor(.red) } else { Image(systemName: "lock.fill") diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index 708584a4..d281dc86 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -176,7 +176,7 @@ struct UserConfig: View { u.shortName = shortName u.longName = longName u.isUnmessagable = isUnmessagable - let adminMessageId = bleManager.saveUser(config: u, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveUser(config: u, fromUser: connectedUser, toUser: node!.user!) if adminMessageId > 0 { hasChanges = false goBack() @@ -188,7 +188,7 @@ struct UserConfig: View { ham.callSign = longName ham.txPower = Int32(txPower) ham.frequency = overrideFrequency - let adminMessageId = bleManager.saveLicensedUser(ham: ham, fromUser: connectedUser, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + let adminMessageId = bleManager.saveLicensedUser(ham: ham, fromUser: connectedUser, toUser: node!.user!) if adminMessageId > 0 { hasChanges = false goBack() diff --git a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift index 188799b9..2b539ef6 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift @@ -292,6 +292,17 @@ public struct AdminMessage: @unchecked Sendable { set {payloadVariant = .removeBackupPreferences(newValue)} } + /// + /// Send an input event to the node. + /// This is used to trigger physical input events like button presses, touch events, etc. + public var sendInputEvent: AdminMessage.InputEvent { + get { + if case .sendInputEvent(let v)? = payloadVariant {return v} + return AdminMessage.InputEvent() + } + set {payloadVariant = .sendInputEvent(newValue)} + } + /// /// Set the owner for this node public var setOwner: User { @@ -663,6 +674,10 @@ public struct AdminMessage: @unchecked Sendable { /// Remove backups of the node's preferences case removeBackupPreferences(AdminMessage.BackupLocation) /// + /// Send an input event to the node. + /// This is used to trigger physical input events like button presses, touch events, etc. + case sendInputEvent(AdminMessage.InputEvent) + /// /// Set the owner for this node case setOwner(User) /// @@ -1014,6 +1029,34 @@ public struct AdminMessage: @unchecked Sendable { } + /// + /// Input event message to be sent to the node. + public struct InputEvent: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// + /// The input event code + public var eventCode: UInt32 = 0 + + /// + /// Keyboard character code + public var kbChar: UInt32 = 0 + + /// + /// The touch X coordinate + public var touchX: UInt32 = 0 + + /// + /// The touch Y coordinate + public var touchY: UInt32 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + } + public init() {} } @@ -1083,6 +1126,10 @@ public struct SharedContact: Sendable { /// Clears the value of `user`. Subsequent reads from it will return its default value. public mutating func clearUser() {self._user = nil} + /// + /// Add this contact to the blocked / ignored list + public var shouldIgnore: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -1215,6 +1262,7 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 24: .standard(proto: "backup_preferences"), 25: .standard(proto: "restore_preferences"), 26: .standard(proto: "remove_backup_preferences"), + 27: .standard(proto: "send_input_event"), 32: .standard(proto: "set_owner"), 33: .standard(proto: "set_channel"), 34: .standard(proto: "set_config"), @@ -1491,6 +1539,19 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .removeBackupPreferences(v) } }() + case 27: try { + var v: AdminMessage.InputEvent? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .sendInputEvent(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .sendInputEvent(v) + } + }() case 32: try { var v: User? var hadOneofValue = false @@ -1872,6 +1933,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .removeBackupPreferences(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularEnumField(value: v, fieldNumber: 26) }() + case .sendInputEvent?: try { + guard case .sendInputEvent(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 27) + }() case .setOwner?: try { guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 32) @@ -2040,6 +2105,56 @@ extension AdminMessage.BackupLocation: SwiftProtobuf._ProtoNameProviding { ] } +extension AdminMessage.InputEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = AdminMessage.protoMessageName + ".InputEvent" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "event_code"), + 2: .standard(proto: "kb_char"), + 3: .standard(proto: "touch_x"), + 4: .standard(proto: "touch_y"), + ] + + 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.eventCode) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.kbChar) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.touchX) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.touchY) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.eventCode != 0 { + try visitor.visitSingularUInt32Field(value: self.eventCode, fieldNumber: 1) + } + if self.kbChar != 0 { + try visitor.visitSingularUInt32Field(value: self.kbChar, fieldNumber: 2) + } + if self.touchX != 0 { + try visitor.visitSingularUInt32Field(value: self.touchX, fieldNumber: 3) + } + if self.touchY != 0 { + try visitor.visitSingularUInt32Field(value: self.touchY, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: AdminMessage.InputEvent, rhs: AdminMessage.InputEvent) -> Bool { + if lhs.eventCode != rhs.eventCode {return false} + if lhs.kbChar != rhs.kbChar {return false} + if lhs.touchX != rhs.touchX {return false} + if lhs.touchY != rhs.touchY {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".HamParameters" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -2127,6 +2242,7 @@ extension SharedContact: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "node_num"), 2: .same(proto: "user"), + 3: .standard(proto: "should_ignore"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -2137,6 +2253,7 @@ extension SharedContact: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa switch fieldNumber { case 1: try { try decoder.decodeSingularUInt32Field(value: &self.nodeNum) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._user) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.shouldIgnore) }() default: break } } @@ -2153,12 +2270,16 @@ extension SharedContact: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa try { if let v = self._user { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + if self.shouldIgnore != false { + try visitor.visitSingularBoolField(value: self.shouldIgnore, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: SharedContact, rhs: SharedContact) -> Bool { if lhs.nodeNum != rhs.nodeNum {return false} if lhs._user != rhs._user {return false} + if lhs.shouldIgnore != rhs.shouldIgnore {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift index 12a57c69..e37bc908 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift @@ -189,6 +189,11 @@ public struct Config: Sendable { /// If true, disable the default blinking LED (LED_PIN) behavior on the device public var ledHeartbeatDisabled: Bool = false + /// + /// Controls buzzer behavior for audio feedback + /// Defaults to ENABLED + public var buzzerMode: Config.DeviceConfig.BuzzerMode = .allEnabled + public var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -406,6 +411,67 @@ public struct Config: Sendable { } + /// + /// Defines buzzer behavior for audio feedback + public enum BuzzerMode: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + + /// + /// Default behavior. + /// Buzzer is enabled for all audio feedback including button presses and alerts. + case allEnabled // = 0 + + /// + /// Disabled. + /// All buzzer audio feedback is disabled. + case disabled // = 1 + + /// + /// Notifications Only. + /// Buzzer is enabled only for notifications and alerts, but not for button presses. + /// External notification config determines the specifics of the notification behavior. + case notificationsOnly // = 2 + + /// + /// Non-notification system buzzer tones only. + /// Buzzer is enabled only for non-notification tones such as button presses, startup, shutdown, but not for alerts. + case systemOnly // = 3 + case UNRECOGNIZED(Int) + + public init() { + self = .allEnabled + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .allEnabled + case 1: self = .disabled + case 2: self = .notificationsOnly + case 3: self = .systemOnly + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .allEnabled: return 0 + case .disabled: return 1 + case .notificationsOnly: return 2 + case .systemOnly: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [Config.DeviceConfig.BuzzerMode] = [ + .allEnabled, + .disabled, + .notificationsOnly, + .systemOnly, + ] + + } + public init() {} } @@ -2063,6 +2129,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl 10: .standard(proto: "disable_triple_click"), 11: .same(proto: "tzdef"), 12: .standard(proto: "led_heartbeat_disabled"), + 13: .standard(proto: "buzzer_mode"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -2082,6 +2149,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl case 10: try { try decoder.decodeSingularBoolField(value: &self.disableTripleClick) }() case 11: try { try decoder.decodeSingularStringField(value: &self.tzdef) }() case 12: try { try decoder.decodeSingularBoolField(value: &self.ledHeartbeatDisabled) }() + case 13: try { try decoder.decodeSingularEnumField(value: &self.buzzerMode) }() default: break } } @@ -2121,6 +2189,9 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if self.ledHeartbeatDisabled != false { try visitor.visitSingularBoolField(value: self.ledHeartbeatDisabled, fieldNumber: 12) } + if self.buzzerMode != .allEnabled { + try visitor.visitSingularEnumField(value: self.buzzerMode, fieldNumber: 13) + } try unknownFields.traverse(visitor: &visitor) } @@ -2136,6 +2207,7 @@ extension Config.DeviceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if lhs.disableTripleClick != rhs.disableTripleClick {return false} if lhs.tzdef != rhs.tzdef {return false} if lhs.ledHeartbeatDisabled != rhs.ledHeartbeatDisabled {return false} + if lhs.buzzerMode != rhs.buzzerMode {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2169,6 +2241,15 @@ extension Config.DeviceConfig.RebroadcastMode: SwiftProtobuf._ProtoNameProviding ] } +extension Config.DeviceConfig.BuzzerMode: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "ALL_ENABLED"), + 1: .same(proto: "DISABLED"), + 2: .same(proto: "NOTIFICATIONS_ONLY"), + 3: .same(proto: "SYSTEM_ONLY"), + ] +} + extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Config.protoMessageName + ".PositionConfig" public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift index 637b20a8..9607abe1 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/device_ui.pb.swift @@ -141,6 +141,10 @@ public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { /// Ukrainian case ukrainian // = 16 + /// + /// Bulgarian + case bulgarian // = 17 + /// /// Simplified Chinese (experimental) case simplifiedChinese // = 30 @@ -173,6 +177,7 @@ public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { case 14: self = .norwegian case 15: self = .slovenian case 16: self = .ukrainian + case 17: self = .bulgarian case 30: self = .simplifiedChinese case 31: self = .traditionalChinese default: self = .UNRECOGNIZED(rawValue) @@ -198,6 +203,7 @@ public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { case .norwegian: return 14 case .slovenian: return 15 case .ukrainian: return 16 + case .bulgarian: return 17 case .simplifiedChinese: return 30 case .traditionalChinese: return 31 case .UNRECOGNIZED(let i): return i @@ -223,6 +229,7 @@ public enum Language: SwiftProtobuf.Enum, Swift.CaseIterable { .norwegian, .slovenian, .ukrainian, + .bulgarian, .simplifiedChinese, .traditionalChinese, ] @@ -502,6 +509,7 @@ extension Language: SwiftProtobuf._ProtoNameProviding { 14: .same(proto: "NORWEGIAN"), 15: .same(proto: "SLOVENIAN"), 16: .same(proto: "UKRAINIAN"), + 17: .same(proto: "BULGARIAN"), 30: .same(proto: "SIMPLIFIED_CHINESE"), 31: .same(proto: "TRADITIONAL_CHINESE"), ] diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index 407d395f..85981376 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -458,6 +458,18 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { /// Reserved ID for future and past use case qwantzTinyArms // = 101 + ///* + /// Lilygo T-Deck Pro + case tDeckPro // = 102 + + ///* + /// Lilygo TLora Pager + case tLoraPager // = 103 + + ///* + /// GAT562 Mesh Trial Tracker + case gat562MeshTrialTracker // = 104 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// 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. @@ -573,6 +585,9 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { case 99: self = .seeedWioTrackerL1 case 100: self = .seeedWioTrackerL1Eink case 101: self = .qwantzTinyArms + case 102: self = .tDeckPro + case 103: self = .tLoraPager + case 104: self = .gat562MeshTrialTracker case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -682,6 +697,9 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { case .seeedWioTrackerL1: return 99 case .seeedWioTrackerL1Eink: return 100 case .qwantzTinyArms: return 101 + case .tDeckPro: return 102 + case .tLoraPager: return 103 + case .gat562MeshTrialTracker: return 104 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -791,6 +809,9 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { .seeedWioTrackerL1, .seeedWioTrackerL1Eink, .qwantzTinyArms, + .tDeckPro, + .tLoraPager, + .gat562MeshTrialTracker, .privateHw, ] @@ -2991,12 +3012,30 @@ public struct ClientNotification: Sendable { set {payloadVariant = .keyVerificationFinal(newValue)} } + public var duplicatedPublicKey: DuplicatedPublicKey { + get { + if case .duplicatedPublicKey(let v)? = payloadVariant {return v} + return DuplicatedPublicKey() + } + set {payloadVariant = .duplicatedPublicKey(newValue)} + } + + public var lowEntropyKey: LowEntropyKey { + get { + if case .lowEntropyKey(let v)? = payloadVariant {return v} + return LowEntropyKey() + } + set {payloadVariant = .lowEntropyKey(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_PayloadVariant: Equatable, Sendable { case keyVerificationNumberInform(KeyVerificationNumberInform) case keyVerificationNumberRequest(KeyVerificationNumberRequest) case keyVerificationFinal(KeyVerificationFinal) + case duplicatedPublicKey(DuplicatedPublicKey) + case lowEntropyKey(LowEntropyKey) } @@ -3053,6 +3092,26 @@ public struct KeyVerificationFinal: Sendable { public init() {} } +public struct DuplicatedPublicKey: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public struct LowEntropyKey: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + /// /// Individual File info for the device public struct FileInfo: Sendable { @@ -3578,6 +3637,9 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 99: .same(proto: "SEEED_WIO_TRACKER_L1"), 100: .same(proto: "SEEED_WIO_TRACKER_L1_EINK"), 101: .same(proto: "QWANTZ_TINY_ARMS"), + 102: .same(proto: "T_DECK_PRO"), + 103: .same(proto: "T_LORA_PAGER"), + 104: .same(proto: "GAT562_MESH_TRIAL_TRACKER"), 255: .same(proto: "PRIVATE_HW"), ] } @@ -5348,6 +5410,8 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 11: .standard(proto: "key_verification_number_inform"), 12: .standard(proto: "key_verification_number_request"), 13: .standard(proto: "key_verification_final"), + 14: .standard(proto: "duplicated_public_key"), + 15: .standard(proto: "low_entropy_key"), ] public mutating func decodeMessage(decoder: inout D) throws { @@ -5399,6 +5463,32 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple self.payloadVariant = .keyVerificationFinal(v) } }() + case 14: try { + var v: DuplicatedPublicKey? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .duplicatedPublicKey(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .duplicatedPublicKey(v) + } + }() + case 15: try { + var v: LowEntropyKey? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .lowEntropyKey(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .lowEntropyKey(v) + } + }() default: break } } @@ -5434,6 +5524,14 @@ extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImple guard case .keyVerificationFinal(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 13) }() + case .duplicatedPublicKey?: try { + guard case .duplicatedPublicKey(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 14) + }() + case .lowEntropyKey?: try { + guard case .lowEntropyKey(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 15) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -5582,6 +5680,44 @@ extension KeyVerificationFinal: SwiftProtobuf.Message, SwiftProtobuf._MessageImp } } +extension DuplicatedPublicKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".DuplicatedPublicKey" + 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 {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: DuplicatedPublicKey, rhs: DuplicatedPublicKey) -> Bool { + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension LowEntropyKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LowEntropyKey" + 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 {} + } + + public func traverse(visitor: inout V) throws { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: LowEntropyKey, rhs: LowEntropyKey) -> Bool { + 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 = [