diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 2c78eec36..b66a20b3f 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -225,6 +225,7 @@ AA003DOC0000002000000003 /* DocPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA003DOC0000001000000003 /* DocPageView.swift */; }; AA003DOC0000002000000004 /* AIDocAssistantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA003DOC0000001000000004 /* AIDocAssistantView.swift */; }; AA003DOC0000002000000005 /* DocBundleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA003DOC0000001000000005 /* DocBundleTests.swift */; }; + DDF1A0022F12345600BEEF01 /* FindNodeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF1A0012F12345600BEEF01 /* FindNodeButton.swift */; }; C54AC9F4DA920A4F718CE21A /* DiscoveryScanEngineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD14FA280F0A130730DEB93 /* DiscoveryScanEngineTests.swift */; }; C79BFC99EDCC89DD45082753 /* DiscoveryScanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5CA8BAC814CF82E127EEABF /* DiscoveryScanView.swift */; }; D02589957BD040969FCB8663 /* PositionEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97241903A924144A3EEB679 /* PositionEntity.swift */; }; @@ -1051,6 +1052,7 @@ DDDB26412AABF655003AFCB7 /* NodeListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeListItem.swift; sourceTree = ""; }; DDDB26432AAC0206003AFCB7 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; DDDB26452AACC0B7003AFCB7 /* NodeInfoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoItem.swift; sourceTree = ""; }; + DDF1A0012F12345600BEEF01 /* FindNodeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNodeButton.swift; sourceTree = ""; }; DDDB26492AAD743E003AFCB7 /* MeshtasticDataModelV18.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV18.xcdatamodel; sourceTree = ""; }; DDDB443F29F79AB000EE2349 /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; DDDB444129F8A88700EE2349 /* Double.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Double.swift; sourceTree = ""; }; @@ -1492,6 +1494,7 @@ children = ( DD4074682F1233F400BCC22F /* ExchangeUserInfoButton.swift */, DDDFE73E2D0D48FF0044463C /* IgnoreNodeButton.swift */, + DDF1A0012F12345600BEEF01 /* FindNodeButton.swift */, BCCA0BAC2F1C5C60007648E5 /* RequestLocalStatsButton.swift */, 251926842C3BA97800249DF5 /* FavoriteNodeButton.swift */, 251926892C3BB1B200249DF5 /* ExchangePositionsButton.swift */, @@ -2746,6 +2749,7 @@ DDDB444C29F8AAA600EE2349 /* Color.swift in Sources */, AA000001303050030000B001 /* Color+Brand.swift in Sources */, DDDFE73F2D0D48FF0044463C /* IgnoreNodeButton.swift in Sources */, + DDF1A0022F12345600BEEF01 /* FindNodeButton.swift in Sources */, BCCA0BAD2F1C5C60007648E5 /* RequestLocalStatsButton.swift in Sources */, 3D3417D42E2DC293006A988B /* MapDataFiles.swift in Sources */, DDB8F4122A9EE5DD00230ECE /* UserList.swift in Sources */, diff --git a/Meshtastic/Accessory/Accessory Manager/AccessoryManager+Connect.swift b/Meshtastic/Accessory/Accessory Manager/AccessoryManager+Connect.swift index 625ecbc10..e3e5a25cf 100644 --- a/Meshtastic/Accessory/Accessory Manager/AccessoryManager+Connect.swift +++ b/Meshtastic/Accessory/Accessory Manager/AccessoryManager+Connect.swift @@ -174,15 +174,12 @@ extension AccessoryManager { throw AccessoryError.connectionFailed("Firmware version not available") } - let lastDotIndex = firmwareVersion.lastIndex(of: ".") - if lastDotIndex == nil { + guard let version = DeviceMetadataEntity.displayFirmwareVersion(from: firmwareVersion), version.contains(".") else { throw AccessoryError.versionMismatch("🚨" + "Update Your Firmware".localized) } - let version = firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: firmwareVersion))].dropLast() - // TODO: do we really need to store the firmware version in the UserDefaults? - UserDefaults.firmwareVersion = String(version) + UserDefaults.firmwareVersion = version let supportedVersion = self.checkIsVersionSupported(forVersion: self.minimumVersion) if !supportedVersion { @@ -228,14 +225,7 @@ extension AccessoryManager { } if let device = self.activeConnection?.device { - var version: String? - if let firmwareVersion = device.firmwareVersion { - if let lastDotIndex = firmwareVersion.lastIndex(of: ".") { - version = String(firmwareVersion[...(lastDotIndex)].dropLast()) - } else { - version = firmwareVersion - } - } + let version = device.firmwareVersion.flatMap(DeviceMetadataEntity.displayFirmwareVersion) let connectionWasRestored = (withConnection != nil) Logger.datadog.action(.connect(firmwareVersion: version, diff --git a/Meshtastic/Accessory/Accessory Manager/AccessoryManager+FromRadio.swift b/Meshtastic/Accessory/Accessory Manager/AccessoryManager+FromRadio.swift index 652c0bc40..3c60b74eb 100644 --- a/Meshtastic/Accessory/Accessory Manager/AccessoryManager+FromRadio.swift +++ b/Meshtastic/Accessory/Accessory Manager/AccessoryManager+FromRadio.swift @@ -212,14 +212,10 @@ extension AccessoryManager { // Local config parses out the variants. Should we do that here maybe? await MeshPackets.shared.localConfig(config: config, nodeNum: Int64(truncatingIfNeeded: deviceNum), nodeLongName: longName) - // Handle Timezone if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { - var dc = config.device - if dc.tzdef.isEmpty { - dc.tzdef = TimeZone.current.posixDescription - Task { - try? await saveTimeZone(config: dc, user: deviceNum) - } + let deviceConfig = config.device + if deviceConfig.tzdef.isEmpty { + Logger.admin.info("Skipping automatic timezone config write during connect sync for node \(deviceNum.toHex(), privacy: .public)") } } } diff --git a/Meshtastic/Accessory/Accessory Manager/AccessoryManager+ToRadio.swift b/Meshtastic/Accessory/Accessory Manager/AccessoryManager+ToRadio.swift index 3f4206e67..8340c7b94 100644 --- a/Meshtastic/Accessory/Accessory Manager/AccessoryManager+ToRadio.swift +++ b/Meshtastic/Accessory/Accessory Manager/AccessoryManager+ToRadio.swift @@ -86,28 +86,6 @@ extension AccessoryManager { } } - public func saveTimeZone(config: Config.DeviceConfig, user: Int64) async throws -> Int64 { - var adminPacket = AdminMessage() - adminPacket.setConfig.device = config - var meshPacket: MeshPacket = MeshPacket() - meshPacket.to = UInt32(user) - meshPacket.from = UInt32(user) - meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { + var request = AdminMessage.FindNodeRequest() + request.durationSeconds = min(durationSeconds, 300) + request.stop = stop + + var adminPacket = AdminMessage() + adminPacket.findNodeRequest = request + if fromUser != toUser { + adminPacket.sessionPasskey = toUser.userNode?.sessionPasskey ?? Data() + } + + var meshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. CBPeripheral { + guard let id = UUID(uuidString: device.identifier) else { + throw AccessoryError.connectionFailed("Invalid BLE peripheral identifier") + } + if let discovered = discoveredPeripherals[id]?.peripheral { + return discovered + } + if let retrieved = centralManager.retrievePeripherals(withIdentifiers: [id]).first { + Logger.transport.info("πŸ›œ [BLE] Retrieved cached peripheral for reconnect: \(retrieved.name ?? device.name, privacy: .public) (\(id, privacy: .public))") + discoveredPeripherals[id] = (peripheral: retrieved, lastSeen: Date()) + return retrieved + } + throw AccessoryError.connectionFailed("Peripheral not found") + } + func handleCentralState(_ state: CBManagerState, central: CBCentralManager) { Logger.transport.error("πŸ›œ [BLE] State has transitioned to: \(cbManagerStateDescription(state), privacy: .public)") switch state { @@ -189,11 +229,11 @@ actor BLETransport: Transport { case .resetting: status = .error("Bluetooth is resetting") - // Perhaps don't finish, wait for next state + Task { await setupCompleteGate.reset() } case .unknown: status = .error("Bluetooth state is unknown") - // Perhaps wait + Task { await setupCompleteGate.reset() } @unknown default: status = .error("Unknown Bluetooth state") Task { await self.setupCompleteGate.throwAll(AccessoryError.connectionFailed("Unknown Bluetooth State"))} @@ -231,10 +271,9 @@ actor BLETransport: Transport { } func connect(to device: Device) async throws -> any Connection { - guard let peripheral = discoveredPeripherals[UUID(uuidString: device.identifier)!] else { - throw AccessoryError.connectionFailed("Peripheral not found") - } - + try await waitForPoweredOnCentral() + let peripheral = try peripheral(for: device) + do { if await self.activeConnection?.peripheral.state == .disconnected { Logger.transport.error("πŸ›œ [BLE] Connect request while an active (but disconnected)") @@ -248,24 +287,32 @@ actor BLETransport: Transport { return } self.connectContinuation = cont - self.connectingPeripheral = peripheral.peripheral + self.connectingPeripheral = peripheral guard centralManager != nil else { + self.connectContinuation = nil + self.connectingPeripheral = nil cont.resume(throwing: AccessoryError.connectionFailed("Bluetooth not initialized")) return } - centralManager.connect(peripheral.peripheral) + guard centralManager.state == .poweredOn else { + self.connectContinuation = nil + self.connectingPeripheral = nil + cont.resume(throwing: AccessoryError.connectionFailed("Bluetooth is not ready: \(cbManagerStateDescription(centralManager.state))")) + return + } + centralManager.connect(peripheral) } self.activeConnection = newConnection return newConnection } onCancel: { Task { - await self.cancelConnectContinuation(for: peripheral.peripheral) + await self.cancelConnectContinuation(for: peripheral) } } Logger.transport.debug("πŸ›œ [BLE] Connect complete.") return returnConnection } catch { - connectionDidDisconnect(fromPeripheral: peripheral.peripheral) + connectionDidDisconnect(fromPeripheral: peripheral) throw error } } diff --git a/Meshtastic/Extensions/SwiftData/DeviceMetadataEntityExtension.swift b/Meshtastic/Extensions/SwiftData/DeviceMetadataEntityExtension.swift index 4352ba6ce..093968fef 100644 --- a/Meshtastic/Extensions/SwiftData/DeviceMetadataEntityExtension.swift +++ b/Meshtastic/Extensions/SwiftData/DeviceMetadataEntityExtension.swift @@ -10,13 +10,10 @@ extension DeviceMetadataEntity { self.canShutdown = metadata.canShutdown self.hasWifi = metadata.hasWifi_p self.hasBluetooth = metadata.hasBluetooth_p + self.hasBuzzer = metadata.hasBuzzer_p self.hasEthernet = metadata.hasEthernet_p self.role = Int32(metadata.role.rawValue) self.positionFlags = Int32(metadata.positionFlags) - // Swift does strings weird, this does work to get the version without the github hash - let lastDotIndex = metadata.firmwareVersion.lastIndex(of: ".") - var version = metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: metadata.firmwareVersion))] - version = version.dropLast() - self.firmwareVersion = String(version) + self.firmwareVersion = Self.displayFirmwareVersion(from: metadata.firmwareVersion) } } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 3868dab1e..62833ffd6 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -15,6 +15,35 @@ import OSLog import ActivityKit #endif +struct FindNodeResponseEvent: Sendable { + enum Result: Sendable { + case started + case stopped + case noBuzzer + case buzzerDisabled + case unrecognized(Int) + + init(_ protoResult: AdminMessage.FindNodeResponse.Result) { + switch protoResult { + case .started: + self = .started + case .stopped: + self = .stopped + case .noBuzzer: + self = .noBuzzer + case .buzzerDisabled: + self = .buzzerDisabled + case .UNRECOGNIZED(let value): + self = .unrecognized(value) + } + } + } + + let nodeNum: Int64 + let result: Result + let durationSeconds: UInt32 +} + // Simple extension to concisely pass values through a has_XXX boolean check fileprivate extension Bool { func then(_ value: T) -> T? { @@ -372,15 +401,12 @@ actor MeshPackets { newMetadata.canShutdown = metadata.canShutdown newMetadata.hasWifi = metadata.hasWifi_p newMetadata.hasBluetooth = metadata.hasBluetooth_p + newMetadata.hasBuzzer = metadata.hasBuzzer_p newMetadata.hasEthernet = metadata.hasEthernet_p newMetadata.role = Int32(metadata.role.rawValue) newMetadata.positionFlags = Int32(metadata.positionFlags) newMetadata.excludedModules = Int32(metadata.excludedModules) - // Swift does strings weird, this does work to get the version without the github hash - let lastDotIndex = metadata.firmwareVersion.lastIndex(of: ".") - var version = metadata.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: metadata.firmwareVersion))] - version = version.dropLast() - newMetadata.firmwareVersion = String(version) + newMetadata.firmwareVersion = DeviceMetadataEntity.displayFirmwareVersion(from: metadata.firmwareVersion) if fetchedNode.count > 0 { fetchedNode[0].metadata = newMetadata if sessionPasskey?.count != 0 { @@ -727,6 +753,15 @@ actor MeshPackets { if let rt = try? RTTTLConfig(serializedBytes: packet.decoded.payload) { self.upsertRtttlConfigPacket(ringtone: rt.ringtone, nodeNum: Int64(packet.from)) } + } else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.findNodeResponse(adminMessage.findNodeResponse) { + let response = adminMessage.findNodeResponse + let event = FindNodeResponseEvent( + nodeNum: Int64(packet.from), + result: FindNodeResponseEvent.Result(response.result), + durationSeconds: response.durationSeconds + ) + NotificationCenter.default.post(name: .findNodeResponseDidChange, object: event) + Logger.admin.info("πŸ”Š Find Node response from \(packet.from.toHex(), privacy: .public): \(String(describing: response.result), privacy: .public)") } else { Logger.admin.error("πŸ•ΈοΈ MESH PACKET received Admin App UNHANDLED \((try? packet.decoded.jsonString()) ?? "JSON Decode Failure", privacy: .public)") } diff --git a/Meshtastic/Model/DeviceMetadataEntity.swift b/Meshtastic/Model/DeviceMetadataEntity.swift index 2112af1ce..7e7e66842 100644 --- a/Meshtastic/Model/DeviceMetadataEntity.swift +++ b/Meshtastic/Model/DeviceMetadataEntity.swift @@ -15,6 +15,7 @@ final class DeviceMetadataEntity { var excludedModules: Int32 = 0 var firmwareVersion: String? var hasBluetooth: Bool = false + var hasBuzzer: Bool = false var hasEthernet: Bool = false var hasWifi: Bool = false var hwModel: String? @@ -25,4 +26,18 @@ final class DeviceMetadataEntity { var metadataNode: NodeInfoEntity? init() {} + + static func displayFirmwareVersion(from rawVersion: String) -> String? { + let version = rawVersion.trimmingCharacters(in: .whitespacesAndNewlines) + guard !version.isEmpty else { + return nil + } + + let components = version.split(separator: ".", omittingEmptySubsequences: false) + guard components.count > 3 else { + return version + } + + return components.dropLast().joined(separator: ".") + } } diff --git a/Meshtastic/Persistence/NodeBackupManager+Import.swift b/Meshtastic/Persistence/NodeBackupManager+Import.swift index ffd91b7f4..7830c7523 100644 --- a/Meshtastic/Persistence/NodeBackupManager+Import.swift +++ b/Meshtastic/Persistence/NodeBackupManager+Import.swift @@ -120,6 +120,7 @@ extension NodeBackupManager { dst.excludedModules = src.excludedModules dst.firmwareVersion = src.firmwareVersion dst.hasBluetooth = src.hasBluetooth + dst.hasBuzzer = src.hasBuzzer dst.hasEthernet = src.hasEthernet dst.hasWifi = src.hasWifi dst.hwModel = src.hwModel diff --git a/Meshtastic/Persistence/PerformanceSeedData.swift b/Meshtastic/Persistence/PerformanceSeedData.swift index c036354db..f523d3453 100644 --- a/Meshtastic/Persistence/PerformanceSeedData.swift +++ b/Meshtastic/Persistence/PerformanceSeedData.swift @@ -144,6 +144,7 @@ enum PerformanceSeedData { metadata.hwModel = user.hwModel metadata.firmwareVersion = "2.7.\(index % 10)" metadata.hasBluetooth = true + metadata.hasBuzzer = index.isMultiple(of: 11) metadata.hasWifi = index.isMultiple(of: 5) metadata.role = user.role metadata.time = node.lastHeard diff --git a/Meshtastic/Views/Connect/Connect.swift b/Meshtastic/Views/Connect/Connect.swift index 805c25c52..44e9436f4 100644 --- a/Meshtastic/Views/Connect/Connect.swift +++ b/Meshtastic/Views/Connect/Connect.swift @@ -841,22 +841,24 @@ func switchToDevice( try? await accessoryManager.disconnect() } - await backupCurrentDatabase(forTargetNode: targetNodeNum, accessoryManager: accessoryManager) - if let targetNodeNum { - let restoreResult = await backupCurrentAndRestoreDatabase( - forNode: targetNodeNum, - accessoryManager: accessoryManager, - appState: appState, - selectedTab: .connect - ) - switch restoreResult { - case .success: - Logger.backup.info("πŸ’Ύ Backup restored for target node \(targetNodeNum)") - case .skipped(let reason): - Logger.backup.warning("πŸ’Ύ Restore skipped: \(reason, privacy: .public)") - case .noBackupFound: - Logger.backup.info("πŸ’Ύ No backup for target node \(targetNodeNum) β€” radio will populate fresh data") + if currentNodeNum == targetNodeNum { + Logger.backup.info("πŸ’Ύ Target node is already active; skipping database backup/restore") + } else { + let restoreResult = await backupCurrentAndRestoreDatabase( + forNode: targetNodeNum, + accessoryManager: accessoryManager, + appState: appState, + selectedTab: .connect + ) + switch restoreResult { + case .success: + Logger.backup.info("πŸ’Ύ Backup restored for target node \(targetNodeNum)") + case .skipped(let reason): + Logger.backup.warning("πŸ’Ύ Restore skipped: \(reason, privacy: .public)") + case .noBackupFound: + Logger.backup.info("πŸ’Ύ No backup for target node \(targetNodeNum) β€” radio will populate fresh data") + } } } else { Logger.backup.warning("πŸ’Ύ Target node num is nil β€” cannot restore, radio will populate fresh data") diff --git a/Meshtastic/Views/Nodes/Helpers/Actions/FindNodeButton.swift b/Meshtastic/Views/Nodes/Helpers/Actions/FindNodeButton.swift new file mode 100644 index 000000000..7e34db6c7 --- /dev/null +++ b/Meshtastic/Views/Nodes/Helpers/Actions/FindNodeButton.swift @@ -0,0 +1,447 @@ +import SwiftUI +import OSLog + +extension NSNotification.Name { + static let findNodeResponseDidChange = NSNotification.Name("findNodeResponseDidChange") +} + +struct FindNodeButton: View { + @EnvironmentObject var accessoryManager: AccessoryManager + @Environment(\.accessibilityReduceMotion) private var reduceMotion + + var fromUser: UserEntity? + var toUser: UserEntity? + var nodeName: String + + @State private var isWaitingForResponse = false + @State private var hasActiveFindNode = false + @State private var activeUntil: Date? + @State private var activeResetTask: Task? + @State private var alert: FindNodeAlert? + + private var hasConnectedTransport: Bool { + guard fromUser != nil, + toUser != nil, + accessoryManager.activeConnection != nil else { + return false + } + + switch accessoryManager.state { + case .communicating, .retrievingDatabase, .subscribed: + return true + case .uninitialized, .idle, .discovering, .connecting, .retrying: + return accessoryManager.isConnected + } + } + + private var isWaitingForSync: Bool { + hasConnectedTransport && accessoryManager.state != .subscribed + } + + private var canSend: Bool { + fromUser != nil + && toUser != nil + && hasConnectedTransport + && !isWaitingForResponse + } + + private var isActive: Bool { + guard hasActiveFindNode else { + return false + } + guard let activeUntil else { + return true + } + return activeUntil > Date.now + } + + var body: some View { + TimelineView(.periodic(from: .now, by: 1.0)) { timeline in + VStack(alignment: .leading, spacing: 18) { + HStack(alignment: .center, spacing: 16) { + FindNodeBeaconView(isActive: isActive, reduceMotion: reduceMotion) + .frame(width: 82, height: 82) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: 6) { + Text("Find Node") + .font(.headline) + Text(statusText(at: timeline.date)) + .font(.subheadline) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + if isActive { + Label("Locating", systemImage: "dot.radiowaves.left.and.right") + .font(.caption.weight(.semibold)) + .foregroundStyle(.orange) + .labelStyle(.titleAndIcon) + } else if isWaitingForSync { + Label("Syncing", systemImage: "arrow.triangle.2.circlepath") + .font(.caption.weight(.medium)) + .foregroundStyle(.secondary) + .labelStyle(.titleAndIcon) + } else if hasConnectedTransport { + Label("Ready", systemImage: "checkmark.circle") + .font(.caption.weight(.medium)) + .foregroundStyle(.secondary) + .labelStyle(.titleAndIcon) + } + } + Spacer(minLength: 0) + } + + HStack(spacing: 12) { + Button { + sendFindNode(stop: false) + } label: { + FindNodeActionLabel( + title: isActive ? "Restart" : "Start Find Node", + systemImage: isActive ? "arrow.clockwise.circle.fill" : "location.circle.fill" + ) + } + .buttonStyle(.borderedProminent) + .controlSize(.large) + .disabled(!canSend) + + if isActive { + Button(role: .cancel) { + sendFindNode(stop: true) + } label: { + FindNodeActionLabel( + title: "Stop", + systemImage: "stop.circle.fill" + ) + } + .buttonStyle(.bordered) + .controlSize(.large) + .disabled(!canSend) + } + } + } + .padding(18) + .background(cardBackground) + .overlay(cardBorder) + .clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous)) + } + .padding(.vertical, 10) + .listRowInsets(EdgeInsets(top: 10, leading: 16, bottom: 10, trailing: 16)) + .listRowBackground(Color.clear) + .accessibilityElement(children: .combine) + .accessibilityLabel("Find Node") + .accessibilityValue(accessibilityValue) + .onChange(of: accessoryManager.isConnected) { _, isConnected in + if !isConnected { + resetActiveState() + } + } + .onChange(of: toUser?.num) { + resetActiveState() + } + .onDisappear { + activeResetTask?.cancel() + } + .alert(item: $alert) { alert in + Alert( + title: Text(alert.title), + message: Text(alert.message), + dismissButton: .default(Text("OK")) + ) + } + } + + private var cardBackground: some ShapeStyle { + LinearGradient( + colors: isActive + ? [Color.orange.opacity(0.20), Color.orange.opacity(0.08), Color.clear] + : [Color.accentColor.opacity(0.12), Color.clear], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + } + + private var cardBorder: some View { + RoundedRectangle(cornerRadius: 24, style: .continuous) + .strokeBorder(isActive ? Color.orange.opacity(0.35) : Color.primary.opacity(0.08), lineWidth: 1) + } + + private var accessibilityValue: String { + if isWaitingForResponse { + return hasActiveFindNode + ? String(localized: "Stopping buzzer") + : String(localized: "Starting buzzer") + } + if isActive { + return String(localized: "Buzzer active, repeating every 2 seconds") + } + if isWaitingForSync { + return String(localized: "Connected, waiting for sync before sending") + } + if !hasConnectedTransport { + return String(localized: "Not connected") + } + return String(localized: "Ready") + } + + private func statusText(at date: Date = .now) -> String { + if isWaitingForResponse { + return hasActiveFindNode + ? String(localized: "Stopping buzzer...") + : String(localized: "Starting buzzer...") + } + if isActive { + if let activeUntil { + let seconds = max(0, Int(activeUntil.timeIntervalSince(date).rounded(.up))) + if seconds > 0 { + return String(localized: "Buzzing every 2 seconds. Stops in \(seconds)s.") + } + } + return String(localized: "Buzzing every 2 seconds") + } + if isWaitingForSync { + return String(localized: "Connected. Request will send after sync finishes.") + } + if !hasConnectedTransport { + return String(localized: "Connect to this node to use Find Node.") + } + return String(localized: "Play this node's buzzer so it is easier to locate nearby.") + } + + private func sendFindNode(stop: Bool) { + guard let fromUser, let toUser else { + alert = FindNodeAlert( + title: String(localized: "Find Node Unavailable"), + message: String(localized: "Connect to a node with administration access, then try again.") + ) + return + } + + isWaitingForResponse = true + let responseTask = Task { + await waitForFindNodeResponse(nodeNum: toUser.num) + } + + Task { + do { + _ = try await accessoryManager.sendFindNodeRequest( + fromUser: fromUser, + toUser: toUser, + stop: stop + ) + await MainActor.run { + isWaitingForResponse = false + if stop { + resetActiveState() + } else { + startActive(durationSeconds: 30) + } + } + } catch { + responseTask.cancel() + await MainActor.run { + isWaitingForResponse = false + alert = FindNodeAlert( + title: String(localized: "Find Node Failed"), + message: findNodeFailureMessage(for: error) + ) + } + Logger.mesh.warning("Failed to send find node request: \(error)") + return + } + + let response = await responseTask.value + await MainActor.run { + isWaitingForResponse = false + handle(response: response, requestedStop: stop) + } + } + } + + private func resetActiveState() { + activeResetTask?.cancel() + activeResetTask = nil + isWaitingForResponse = false + hasActiveFindNode = false + activeUntil = nil + } + + private func startActive(durationSeconds: UInt32) { + hasActiveFindNode = true + activeUntil = Date.now.addingTimeInterval(TimeInterval(durationSeconds)) + scheduleActiveReset(after: durationSeconds) + } + + private func scheduleActiveReset(after durationSeconds: UInt32) { + activeResetTask?.cancel() + activeResetTask = Task { + try? await Task.sleep(for: .seconds(durationSeconds)) + guard !Task.isCancelled else { + return + } + await MainActor.run { + hasActiveFindNode = false + activeUntil = nil + activeResetTask = nil + } + } + } + + private func waitForFindNodeResponse(nodeNum: Int64) async -> FindNodeResponseEvent? { + await withTaskGroup(of: FindNodeResponseEvent?.self) { group in + group.addTask { + for await notification in NotificationCenter.default.notifications(named: .findNodeResponseDidChange) { + guard let event = notification.object as? FindNodeResponseEvent, + event.nodeNum == nodeNum else { + continue + } + return event + } + return nil + } + group.addTask { + try? await Task.sleep(for: .seconds(30)) + return nil + } + + let result = await group.next() ?? nil + group.cancelAll() + return result + } + } + + private func handle(response: FindNodeResponseEvent?, requestedStop: Bool) { + guard let response else { + Logger.mesh.warning("Find Node request sent but no response was received for \(nodeName, privacy: .public)") + return + } + + switch response.result { + case .started: + let duration = response.durationSeconds == 0 ? 30 : response.durationSeconds + startActive(durationSeconds: duration) + case .stopped: + resetActiveState() + case .noBuzzer: + resetActiveState() + alert = FindNodeAlert( + title: String(localized: "No Buzzer Detected"), + message: "Firmware did not detect a supported buzzer output on \(nodeName)." + ) + case .buzzerDisabled: + resetActiveState() + alert = FindNodeAlert( + title: String(localized: "Buzzer Disabled"), + message: "\(nodeName) has buzzer hardware, but buzzer mode is disabled." + ) + case .unrecognized: + resetActiveState() + alert = FindNodeAlert( + title: String(localized: "Unknown Find Node Response"), + message: String(localized: "Firmware returned a Find Node response this app does not recognize.") + ) + } + } + + private struct FindNodeAlert: Identifiable { + let id = UUID() + let title: String + let message: String + } + + private func findNodeFailureMessage(for error: Error) -> String { + if let localizedError = error as? LocalizedError, + let errorDescription = localizedError.errorDescription { + return errorDescription + } + return String(localized: "The request could not be sent. Check the connection and try again.") + } +} + +private struct FindNodeActionLabel: View { + let title: LocalizedStringKey + let systemImage: String + + var body: some View { + HStack(spacing: 8) { + Image(systemName: systemImage) + .imageScale(.medium) + .frame(width: 20) + .accessibilityHidden(true) + Text(title) + .lineLimit(1) + .minimumScaleFactor(0.85) + } + .frame(maxWidth: .infinity, minHeight: 28, alignment: .center) + } +} + +private struct FindNodeBeaconView: View { + let isActive: Bool + let reduceMotion: Bool + private let cadence: TimeInterval = 2.0 + + var body: some View { + TimelineView(.animation(minimumInterval: 1.0 / 30.0, paused: !isActive || reduceMotion)) { timeline in + let phase = isActive && !reduceMotion + ? timeline.date.timeIntervalSinceReferenceDate.truncatingRemainder(dividingBy: cadence) / cadence + : 0 + let pulse = isActive && !reduceMotion + ? 0.5 + 0.5 * sin(phase * .pi * 2.0) + : 0.0 + + ZStack { + if isActive { + ForEach(0..<3, id: \.self) { index in + RippleRing(progress: (phase + Double(index) / 3.0).truncatingRemainder(dividingBy: 1.0)) + } + + Circle() + .fill(Color.orange.opacity(0.08 + 0.06 * pulse)) + .frame(width: 74, height: 74) + .scaleEffect(0.94 + 0.08 * pulse) + } + + Circle() + .fill(.regularMaterial) + .background { + Circle() + .fill( + RadialGradient( + colors: isActive + ? [Color.orange.opacity(0.30), Color.orange.opacity(0.10), Color.clear] + : [Color.accentColor.opacity(0.18), Color.clear], + center: .center, + startRadius: 4, + endRadius: 42 + ) + ) + } + .overlay { + Circle() + .fill(isActive ? Color.orange.opacity(0.14) : Color.accentColor.opacity(0.12)) + } + .overlay { + Circle() + .strokeBorder(isActive ? Color.orange.opacity(0.36) : Color.accentColor.opacity(0.22), lineWidth: 1) + } + .frame(width: 58, height: 58) + .shadow(color: isActive ? Color.orange.opacity(0.32) : Color.clear, radius: 18, y: 6) + + Image(systemName: isActive ? "location.fill" : "location.circle.fill") + .font(.system(size: 24, weight: .semibold)) + .symbolRenderingMode(.palette) + .foregroundStyle(isActive ? Color.orange : Color.accentColor, .white.opacity(0.9)) + .symbolEffect(.pulse, options: .speed(0.5), isActive: isActive && !reduceMotion) + } + } + } + + private struct RippleRing: View { + let progress: TimeInterval + + var body: some View { + Circle() + .stroke(Color.orange.opacity(max(0, 0.34 * pow(1 - progress, 1.5))), lineWidth: 2) + .scaleEffect(0.52 + progress * 0.72) + } + } +} diff --git a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift index 63da281fc..f9e022f94 100644 --- a/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/Helpers/NodeDetail.swift @@ -632,29 +632,6 @@ struct NodeDetail: View { accessoryManager.isConnected { Section("Administration") { let administrationUserPair = self.administrationUserPair - if UserDefaults.enableAdministration { - Button { - Task { - guard let administrationUserPair else { return } - do { - _ = try await accessoryManager.requestDeviceMetadata( - fromUser: administrationUserPair.fromUser, - toUser: administrationUserPair.toUser - ) - Logger.mesh.info("Sent node metadata request from node details") - } catch { - Logger.mesh.error("Failed to send node metadata request from node details") - } - } - } label: { - Label { - Text("Refresh device metadata") - } icon: { - Image(systemName: "arrow.clockwise") - } - } - .disabled(administrationUserPair == nil) - } if metadata.canShutdown { Button { showingShutdownConfirm = true diff --git a/Meshtastic/Views/Settings/Tools.swift b/Meshtastic/Views/Settings/Tools.swift index 723787ffa..7180828d7 100644 --- a/Meshtastic/Views/Settings/Tools.swift +++ b/Meshtastic/Views/Settings/Tools.swift @@ -50,6 +50,33 @@ struct Tools: View { var body: some View { VStack { List { + Section(header: Text("Find Node")) { + if let node = connectedNode { + if node.metadata?.hasBuzzer == true { + FindNodeButton( + fromUser: node.user, + toUser: node.user, + nodeName: node.user?.longName ?? "this node" + ) + } else { + Label { + Text("This connected node does not report buzzer hardware.") + .foregroundStyle(.secondary) + } icon: { + Image(systemName: "speaker.slash") + .symbolRenderingMode(.hierarchical) + } + } + } else { + Label { + Text("Connect to a node to use Find Node.") + .foregroundStyle(.secondary) + } icon: { + Image(systemName: "wifi.slash") + .symbolRenderingMode(.hierarchical) + } + } + } Section(header: Text("Create Node Contact NFC Tag")) { if let node = connectedNode { Text("Node Name: \(node.user?.longName ?? "Unknown".localized)") diff --git a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift index dd466b903..42cbe815d 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/admin.pb.swift @@ -584,6 +584,26 @@ public struct AdminMessage: @unchecked Sendable { set {_uniqueStorage()._payloadVariant = .keyVerification(newValue)} } + /// + /// Start or stop the audible find-node pattern. + public var findNodeRequest: AdminMessage.FindNodeRequest { + get { + if case .findNodeRequest(let v)? = _storage._payloadVariant {return v} + return AdminMessage.FindNodeRequest() + } + set {_uniqueStorage()._payloadVariant = .findNodeRequest(newValue)} + } + + /// + /// Result of a find-node request. + public var findNodeResponse: AdminMessage.FindNodeResponse { + get { + if case .findNodeResponse(let v)? = _storage._payloadVariant {return v} + return AdminMessage.FindNodeResponse() + } + set {_uniqueStorage()._payloadVariant = .findNodeResponse(newValue)} + } + /// /// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. public var factoryResetDevice: Int32 { @@ -856,6 +876,12 @@ public struct AdminMessage: @unchecked Sendable { /// Initiate or respond to a key verification request case keyVerification(KeyVerificationAdmin) /// + /// Start or stop the audible find-node pattern. + case findNodeRequest(AdminMessage.FindNodeRequest) + /// + /// Result of a find-node request. + case findNodeResponse(AdminMessage.FindNodeResponse) + /// /// Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. case factoryResetDevice(Int32) /// @@ -1229,6 +1255,88 @@ public struct AdminMessage: @unchecked Sendable { public init() {} } + /// + /// Request that the node emit an audible find-node pattern. + public struct FindNodeRequest: 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. + + /// + /// Duration to repeat the find-node pattern. Zero uses the firmware default. + public var durationSeconds: UInt32 = 0 + + /// + /// Stop any active find-node pattern instead of starting a new one. + public var stop: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + } + + /// + /// Response to a find-node request. + public struct FindNodeResponse: 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. + + /// + /// Result of the request. + public var result: AdminMessage.FindNodeResponse.Result = .started + + /// + /// Accepted duration in seconds. Zero for stop or failed requests. + public var durationSeconds: UInt32 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public enum Result: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case started // = 0 + case stopped // = 1 + case noBuzzer // = 2 + case buzzerDisabled // = 3 + case UNRECOGNIZED(Int) + + public init() { + self = .started + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .started + case 1: self = .stopped + case 2: self = .noBuzzer + case 3: self = .buzzerDisabled + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .started: return 0 + case .stopped: return 1 + case .noBuzzer: return 2 + case .buzzerDisabled: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [AdminMessage.FindNodeResponse.Result] = [ + .started, + .stopped, + .noBuzzer, + .buzzerDisabled, + ] + + } + + public init() {} + } + public init() {} fileprivate var _storage = _StorageClass.defaultInstance @@ -1279,6 +1387,67 @@ public struct LockdownAuth: Sendable { /// the locked state. Always honoured regardless of current lock state. public var lockNow: Bool = false + /// + /// Optional per-boot uptime cap on the unlocked session, in seconds. + /// 0 = unlimited (token-only enforcement, suitable for unattended + /// tower / infrastructure nodes). + /// + /// When non-zero, the firmware arms an uptime timer at unlock. On + /// each expiry, while there is still boot-count budget, the firmware + /// decrements the on-flash boot count in place, revokes per- + /// connection admin auth (clients must re-authenticate to see + /// content), re-engages the screen lock, and re-arms the timer + /// without rebooting. Mesh routing keeps running across session + /// boundaries; only when the boot-count budget reaches zero does + /// the device hard-lock and reboot. + /// + /// Total exposure ceiling = ((resolved boot count) + 1) * max_session_seconds. + /// The +1 accounts for the initial passphrase-unlocked session + /// itself, since boots_remaining is the number of subsequent + /// session rolls (each consuming one boot from the rollback ledger). + /// The resolved boot count is the value the firmware writes into the + /// token at unlock time: the client-supplied boots_remaining when + /// non-zero, otherwise the firmware default (TOKEN_DEFAULT_BOOTS). + /// Note that boots_remaining == 0 in this message means "use firmware + /// default", NOT "zero boots" β€” a client computing the ceiling for + /// display should mirror that resolution rather than multiplying the + /// raw request value. + /// + /// The cap is persisted in the token, so it survives token-based + /// auto-unlock across reboots. Explicit operator Lock Now still + /// deletes the token and forces passphrase re-entry. + /// + /// Uses millis() (CPU uptime), not wall-clock time, so the cap is + /// immune to GPS spoofing, RTC backup-battery removal, and Faraday + /// cage isolation β€” none of those move the uptime counter. The only + /// way to reset the session clock is a reboot, which costs a boot + /// from the on-flash, HMAC-bound counter. + public var maxSessionSeconds: UInt32 = 0 + + /// + /// Disable lockdown mode. Requires a valid passphrase in the same + /// message (the device must prove the operator owns it before + /// reverting at-rest encryption). On success the firmware decrypts + /// every stored config / channel / nodedb file back to plaintext, + /// removes the wrapped DEK, unlock token, monotonic-counter, and + /// backoff files, and reboots out of lockdown. + /// + /// This is the inverse of the provision/unlock path: it is how the + /// client app's "lockdown mode" toggle returns a device to normal + /// operation. + /// + /// NOT reversed by this operation: APPROTECT. Once the debug port + /// lockout has been burned (on silicon where it is effective) it is + /// permanent β€” disabling lockdown decrypts your data and removes the + /// access gates, but the SWD/JTAG port stays locked for the life of + /// the device (recoverable only via a full chip erase over a debug + /// probe, which destroys all data). Clients should make this + /// irreversibility clear at the moment lockdown is first enabled. + /// + /// When true the passphrase field is still required; boots_remaining, + /// valid_until_epoch, max_session_seconds, and lock_now are ignored. + public var disable: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -1309,6 +1478,11 @@ public struct HamParameters: Sendable { /// Optional short name of user public var shortName: String = String() + /// + /// Optional long name of user + /// Appended to callsign + public var longName: String = String() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -1762,7 +1936,7 @@ extension OTAMode: SwiftProtobuf._ProtoNameProviding { extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".AdminMessage" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}get_channel_request\0\u{3}get_channel_response\0\u{3}get_owner_request\0\u{3}get_owner_response\0\u{3}get_config_request\0\u{3}get_config_response\0\u{3}get_module_config_request\0\u{3}get_module_config_response\0\u{4}\u{2}get_canned_message_module_messages_request\0\u{3}get_canned_message_module_messages_response\0\u{3}get_device_metadata_request\0\u{3}get_device_metadata_response\0\u{3}get_ringtone_request\0\u{3}get_ringtone_response\0\u{3}get_device_connection_status_request\0\u{3}get_device_connection_status_response\0\u{3}set_ham_mode\0\u{3}get_node_remote_hardware_pins_request\0\u{3}get_node_remote_hardware_pins_response\0\u{3}enter_dfu_mode_request\0\u{3}delete_file_request\0\u{3}set_scale\0\u{3}backup_preferences\0\u{3}restore_preferences\0\u{3}remove_backup_preferences\0\u{3}send_input_event\0\u{4}\u{5}set_owner\0\u{3}set_channel\0\u{3}set_config\0\u{3}set_module_config\0\u{3}set_canned_message_module_messages\0\u{3}set_ringtone_message\0\u{3}remove_by_nodenum\0\u{3}set_favorite_node\0\u{3}remove_favorite_node\0\u{3}set_fixed_position\0\u{3}remove_fixed_position\0\u{3}set_time_only\0\u{3}get_ui_config_request\0\u{3}get_ui_config_response\0\u{3}store_ui_config\0\u{3}set_ignored_node\0\u{3}remove_ignored_node\0\u{3}toggle_muted_node\0\u{4}\u{f}begin_edit_settings\0\u{3}commit_edit_settings\0\u{3}add_contact\0\u{3}key_verification\0\u{4}\u{1b}factory_reset_device\0\u{3}reboot_ota_seconds\0\u{3}exit_simulator\0\u{3}reboot_seconds\0\u{3}shutdown_seconds\0\u{3}factory_reset_config\0\u{3}nodedb_reset\0\u{3}session_passkey\0\u{3}ota_request\0\u{3}sensor_config\0\u{3}lockdown_auth\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}get_channel_request\0\u{3}get_channel_response\0\u{3}get_owner_request\0\u{3}get_owner_response\0\u{3}get_config_request\0\u{3}get_config_response\0\u{3}get_module_config_request\0\u{3}get_module_config_response\0\u{4}\u{2}get_canned_message_module_messages_request\0\u{3}get_canned_message_module_messages_response\0\u{3}get_device_metadata_request\0\u{3}get_device_metadata_response\0\u{3}get_ringtone_request\0\u{3}get_ringtone_response\0\u{3}get_device_connection_status_request\0\u{3}get_device_connection_status_response\0\u{3}set_ham_mode\0\u{3}get_node_remote_hardware_pins_request\0\u{3}get_node_remote_hardware_pins_response\0\u{3}enter_dfu_mode_request\0\u{3}delete_file_request\0\u{3}set_scale\0\u{3}backup_preferences\0\u{3}restore_preferences\0\u{3}remove_backup_preferences\0\u{3}send_input_event\0\u{4}\u{5}set_owner\0\u{3}set_channel\0\u{3}set_config\0\u{3}set_module_config\0\u{3}set_canned_message_module_messages\0\u{3}set_ringtone_message\0\u{3}remove_by_nodenum\0\u{3}set_favorite_node\0\u{3}remove_favorite_node\0\u{3}set_fixed_position\0\u{3}remove_fixed_position\0\u{3}set_time_only\0\u{3}get_ui_config_request\0\u{3}get_ui_config_response\0\u{3}store_ui_config\0\u{3}set_ignored_node\0\u{3}remove_ignored_node\0\u{3}toggle_muted_node\0\u{4}\u{f}begin_edit_settings\0\u{3}commit_edit_settings\0\u{3}add_contact\0\u{3}key_verification\0\u{3}find_node_request\0\u{3}find_node_response\0\u{4}\u{19}factory_reset_device\0\u{3}reboot_ota_seconds\0\u{3}exit_simulator\0\u{3}reboot_seconds\0\u{3}shutdown_seconds\0\u{3}factory_reset_config\0\u{3}nodedb_reset\0\u{3}session_passkey\0\u{3}ota_request\0\u{3}sensor_config\0\u{3}lockdown_auth\0") fileprivate class _StorageClass { var _sessionPasskey: Data = Data() @@ -2271,6 +2445,32 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat _storage._payloadVariant = .keyVerification(v) } }() + case 68: try { + var v: AdminMessage.FindNodeRequest? + var hadOneofValue = false + if let current = _storage._payloadVariant { + hadOneofValue = true + if case .findNodeRequest(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._payloadVariant = .findNodeRequest(v) + } + }() + case 69: try { + var v: AdminMessage.FindNodeResponse? + var hadOneofValue = false + if let current = _storage._payloadVariant { + hadOneofValue = true + if case .findNodeResponse(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._payloadVariant = .findNodeResponse(v) + } + }() case 94: try { var v: Int32? try decoder.decodeSingularInt32Field(value: &v) @@ -2572,6 +2772,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .keyVerification(let v)? = _storage._payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 67) }() + case .findNodeRequest?: try { + guard case .findNodeRequest(let v)? = _storage._payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 68) + }() + case .findNodeResponse?: try { + guard case .findNodeResponse(let v)? = _storage._payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 69) + }() case .factoryResetDevice?: try { guard case .factoryResetDevice(let v)? = _storage._payloadVariant else { preconditionFailure() } try visitor.visitSingularInt32Field(value: v, fieldNumber: 94) @@ -2732,9 +2940,83 @@ extension AdminMessage.OTAEvent: SwiftProtobuf.Message, SwiftProtobuf._MessageIm } } +extension AdminMessage.FindNodeRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = AdminMessage.protoMessageName + ".FindNodeRequest" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}duration_seconds\0\u{1}stop\0") + + 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.durationSeconds) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self.stop) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.durationSeconds != 0 { + try visitor.visitSingularUInt32Field(value: self.durationSeconds, fieldNumber: 1) + } + if self.stop != false { + try visitor.visitSingularBoolField(value: self.stop, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: AdminMessage.FindNodeRequest, rhs: AdminMessage.FindNodeRequest) -> Bool { + if lhs.durationSeconds != rhs.durationSeconds {return false} + if lhs.stop != rhs.stop {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension AdminMessage.FindNodeResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = AdminMessage.protoMessageName + ".FindNodeResponse" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}result\0\u{3}duration_seconds\0") + + 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.decodeSingularEnumField(value: &self.result) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.durationSeconds) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.result != .started { + try visitor.visitSingularEnumField(value: self.result, fieldNumber: 1) + } + if self.durationSeconds != 0 { + try visitor.visitSingularUInt32Field(value: self.durationSeconds, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: AdminMessage.FindNodeResponse, rhs: AdminMessage.FindNodeResponse) -> Bool { + if lhs.result != rhs.result {return false} + if lhs.durationSeconds != rhs.durationSeconds {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension AdminMessage.FindNodeResponse.Result: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0STARTED\0\u{1}STOPPED\0\u{1}NO_BUZZER\0\u{1}BUZZER_DISABLED\0") +} + extension LockdownAuth: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".LockdownAuth" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}passphrase\0\u{3}boots_remaining\0\u{3}valid_until_epoch\0\u{3}lock_now\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}passphrase\0\u{3}boots_remaining\0\u{3}valid_until_epoch\0\u{3}lock_now\0\u{3}max_session_seconds\0\u{1}disable\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2746,6 +3028,8 @@ extension LockdownAuth: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat case 2: try { try decoder.decodeSingularUInt32Field(value: &self.bootsRemaining) }() case 3: try { try decoder.decodeSingularUInt32Field(value: &self.validUntilEpoch) }() case 4: try { try decoder.decodeSingularBoolField(value: &self.lockNow) }() + case 5: try { try decoder.decodeSingularUInt32Field(value: &self.maxSessionSeconds) }() + case 6: try { try decoder.decodeSingularBoolField(value: &self.disable) }() default: break } } @@ -2764,6 +3048,12 @@ extension LockdownAuth: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if self.lockNow != false { try visitor.visitSingularBoolField(value: self.lockNow, fieldNumber: 4) } + if self.maxSessionSeconds != 0 { + try visitor.visitSingularUInt32Field(value: self.maxSessionSeconds, fieldNumber: 5) + } + if self.disable != false { + try visitor.visitSingularBoolField(value: self.disable, fieldNumber: 6) + } try unknownFields.traverse(visitor: &visitor) } @@ -2772,6 +3062,8 @@ extension LockdownAuth: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if lhs.bootsRemaining != rhs.bootsRemaining {return false} if lhs.validUntilEpoch != rhs.validUntilEpoch {return false} if lhs.lockNow != rhs.lockNow {return false} + if lhs.maxSessionSeconds != rhs.maxSessionSeconds {return false} + if lhs.disable != rhs.disable {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2779,7 +3071,7 @@ extension LockdownAuth: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".HamParameters" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}call_sign\0\u{3}tx_power\0\u{1}frequency\0\u{3}short_name\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}call_sign\0\u{3}tx_power\0\u{1}frequency\0\u{3}short_name\0\u{3}long_name\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -2791,6 +3083,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa case 2: try { try decoder.decodeSingularInt32Field(value: &self.txPower) }() case 3: try { try decoder.decodeSingularFloatField(value: &self.frequency) }() case 4: try { try decoder.decodeSingularStringField(value: &self.shortName) }() + case 5: try { try decoder.decodeSingularStringField(value: &self.longName) }() default: break } } @@ -2809,6 +3102,9 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if !self.shortName.isEmpty { try visitor.visitSingularStringField(value: self.shortName, fieldNumber: 4) } + if !self.longName.isEmpty { + try visitor.visitSingularStringField(value: self.longName, fieldNumber: 5) + } try unknownFields.traverse(visitor: &visitor) } @@ -2817,6 +3113,7 @@ extension HamParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementa if lhs.txPower != rhs.txPower {return false} if lhs.frequency != rhs.frequency {return false} if lhs.shortName != rhs.shortName {return false} + if lhs.longName != rhs.longName {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 7dab7bf3b..efc2accb9 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/config.pb.swift @@ -1636,6 +1636,27 @@ public struct Config: Sendable { /// /// ITU Region 3 Amateur Radio 2m band (144-148 MHz) case itu32M // = 33 + + /// + /// ITU Region 1 Amateur Radio 70cm band (430-440 MHz) + case itu170Cm // = 34 + + /// + /// ITU Region 2 Amateur Radio 70cm band (420-450 MHz) + /// Note: Some countries do not allocate 420-430 MHz or 440-450 MHz. + /// Check local law! + case itu270Cm // = 35 + + /// + /// ITU Region 3 Amateur Radio 70cm band (430-450 MHz) + /// Note: Some countries do not allocate 440-450 MHz. Check local law! + case itu370Cm // = 36 + + /// + /// ITU Region 2 Amateur Radio 1.25m '125cm' band (220-225 MHz) + /// Note: Some countries do not allocate 220-222 MHz (Ex: USA/Canada). + /// Check local law! + case itu2125Cm // = 37 case UNRECOGNIZED(Int) public init() { @@ -1678,6 +1699,10 @@ public struct Config: Sendable { case 31: self = .eu917 case 32: self = .euN868 case 33: self = .itu32M + case 34: self = .itu170Cm + case 35: self = .itu270Cm + case 36: self = .itu370Cm + case 37: self = .itu2125Cm default: self = .UNRECOGNIZED(rawValue) } } @@ -1718,6 +1743,10 @@ public struct Config: Sendable { case .eu917: return 31 case .euN868: return 32 case .itu32M: return 33 + case .itu170Cm: return 34 + case .itu270Cm: return 35 + case .itu370Cm: return 36 + case .itu2125Cm: return 37 case .UNRECOGNIZED(let i): return i } } @@ -1758,6 +1787,10 @@ public struct Config: Sendable { .eu917, .euN868, .itu32M, + .itu170Cm, + .itu270Cm, + .itu370Cm, + .itu2125Cm, ] } @@ -1841,6 +1874,24 @@ public struct Config: Sendable { /// Moderate range preset optimized for EU 868MHz band with 62.5kHz bandwidth. /// Comparable link budget and data rate to LONG_FAST. case narrowSlow // = 13 + + /// + /// Tiny Fast + /// Preset optimized for compliance with Amateur Radio restrictions with 20kHz bandwidth. + /// Many regions limit data transmission bandwidth in lower amateur bands (2 Meter). + /// Note: TCXO with tight tolerances (Β±5 ppm or better) is *absolutely required* at these narrow bandwidths. + /// Only compatible with SX127x and SX126x chipsets. + /// Comparable link budget and data rate to LONG_FAST. + case tinyFast // = 14 + + /// + /// Tiny Slow + /// Preset optimized for compliance with Amateur Radio restrictions with 20kHz bandwidth. + /// Many regions limit data transmission bandwidth in lower amateur bands (2 Meter). + /// Note: TCXO with tight tolerances (Β±5 ppm or better) is *absolutely required* at these narrow bandwidths. + /// Only compatible with SX127x and SX126x chipsets. + /// Comparable link budget and data rate to LONG_MODERATE. + case tinySlow // = 15 case UNRECOGNIZED(Int) public init() { @@ -1863,6 +1914,8 @@ public struct Config: Sendable { case 11: self = .liteSlow case 12: self = .narrowFast case 13: self = .narrowSlow + case 14: self = .tinyFast + case 15: self = .tinySlow default: self = .UNRECOGNIZED(rawValue) } } @@ -1883,6 +1936,8 @@ public struct Config: Sendable { case .liteSlow: return 11 case .narrowFast: return 12 case .narrowSlow: return 13 + case .tinyFast: return 14 + case .tinySlow: return 15 case .UNRECOGNIZED(let i): return i } } @@ -1903,6 +1958,8 @@ public struct Config: Sendable { .liteSlow, .narrowFast, .narrowSlow, + .tinyFast, + .tinySlow, ] } @@ -3004,11 +3061,11 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem } extension Config.LoRaConfig.RegionCode: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSET\0\u{1}US\0\u{1}EU_433\0\u{1}EU_868\0\u{1}CN\0\u{1}JP\0\u{1}ANZ\0\u{1}KR\0\u{1}TW\0\u{1}RU\0\u{1}IN\0\u{1}NZ_865\0\u{1}TH\0\u{1}LORA_24\0\u{1}UA_433\0\u{1}UA_868\0\u{1}MY_433\0\u{1}MY_919\0\u{1}SG_923\0\u{1}PH_433\0\u{1}PH_868\0\u{1}PH_915\0\u{1}ANZ_433\0\u{1}KZ_433\0\u{1}KZ_863\0\u{1}NP_865\0\u{1}BR_902\0\u{1}ITU1_2M\0\u{1}ITU2_2M\0\u{1}EU_866\0\u{1}EU_874\0\u{1}EU_917\0\u{1}EU_N_868\0\u{1}ITU3_2M\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSET\0\u{1}US\0\u{1}EU_433\0\u{1}EU_868\0\u{1}CN\0\u{1}JP\0\u{1}ANZ\0\u{1}KR\0\u{1}TW\0\u{1}RU\0\u{1}IN\0\u{1}NZ_865\0\u{1}TH\0\u{1}LORA_24\0\u{1}UA_433\0\u{1}UA_868\0\u{1}MY_433\0\u{1}MY_919\0\u{1}SG_923\0\u{1}PH_433\0\u{1}PH_868\0\u{1}PH_915\0\u{1}ANZ_433\0\u{1}KZ_433\0\u{1}KZ_863\0\u{1}NP_865\0\u{1}BR_902\0\u{1}ITU1_2M\0\u{1}ITU2_2M\0\u{1}EU_866\0\u{1}EU_874\0\u{1}EU_917\0\u{1}EU_N_868\0\u{1}ITU3_2M\0\u{1}ITU1_70CM\0\u{1}ITU2_70CM\0\u{1}ITU3_70CM\0\u{1}ITU2_125CM\0") } extension Config.LoRaConfig.ModemPreset: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0LONG_FAST\0\u{1}LONG_SLOW\0\u{1}VERY_LONG_SLOW\0\u{1}MEDIUM_SLOW\0\u{1}MEDIUM_FAST\0\u{1}SHORT_SLOW\0\u{1}SHORT_FAST\0\u{1}LONG_MODERATE\0\u{1}SHORT_TURBO\0\u{1}LONG_TURBO\0\u{1}LITE_FAST\0\u{1}LITE_SLOW\0\u{1}NARROW_FAST\0\u{1}NARROW_SLOW\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0LONG_FAST\0\u{1}LONG_SLOW\0\u{1}VERY_LONG_SLOW\0\u{1}MEDIUM_SLOW\0\u{1}MEDIUM_FAST\0\u{1}SHORT_SLOW\0\u{1}SHORT_FAST\0\u{1}LONG_MODERATE\0\u{1}SHORT_TURBO\0\u{1}LONG_TURBO\0\u{1}LITE_FAST\0\u{1}LITE_SLOW\0\u{1}NARROW_FAST\0\u{1}NARROW_SLOW\0\u{1}TINY_FAST\0\u{1}TINY_SLOW\0") } extension Config.LoRaConfig.FEM_LNA_Mode: SwiftProtobuf._ProtoNameProviding { diff --git a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift index cee33f815..6975ff8e0 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/deviceonly.pb.swift @@ -53,6 +53,10 @@ public struct PositionLite: Sendable { /// TODO: REPLACE public var locationSource: Position.LocSource = .locUnset + /// + /// Indicates the bits of precision set by the sending node + public var precisionBits: UInt32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -118,129 +122,79 @@ public struct UserLite: Sendable { fileprivate var _isUnmessagable: Bool? = nil } -public struct NodeInfoLite: @unchecked Sendable { +public struct NodeInfoLite: 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 node number - public var num: UInt32 { - get {return _storage._num} - set {_uniqueStorage()._num = newValue} - } + public var num: UInt32 = 0 /// - /// The user info for this node - public var user: UserLite { - get {return _storage._user ?? UserLite()} - set {_uniqueStorage()._user = newValue} - } - /// Returns true if `user` has been explicitly set. - public var hasUser: Bool {return _storage._user != nil} - /// Clears the value of `user`. Subsequent reads from it will return its default value. - public mutating func clearUser() {_uniqueStorage()._user = nil} + /// In-memory SNR of the last received message in dB. Not serialised directly: + /// always zeroed before encode; persisted as snr_q4 = 19 below. + public var snr: Float = 0 /// - /// This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true. - /// Position.time now indicates the last time we received a POSITION from that node. - public var position: PositionLite { - get {return _storage._position ?? PositionLite()} - set {_uniqueStorage()._position = newValue} - } - /// Returns true if `position` has been explicitly set. - public var hasPosition: Bool {return _storage._position != nil} - /// Clears the value of `position`. Subsequent reads from it will return its default value. - public mutating func clearPosition() {_uniqueStorage()._position = nil} + /// Set to indicate the last time we received a packet from this node + public var lastHeard: UInt32 = 0 /// - /// Returns the Signal-to-noise ratio (SNR) of the last received message, - /// as measured by the receiver. Return SNR of the last received message in dB - public var snr: Float { - get {return _storage._snr} - set {_uniqueStorage()._snr = newValue} - } + /// local channel index we heard that node on. Only populated if its not the default channel. + public var channel: UInt32 = 0 /// - /// Set to indicate the last time we received a packet from this node - public var lastHeard: UInt32 { - get {return _storage._lastHeard} - set {_uniqueStorage()._lastHeard = newValue} + /// Number of hops away from us this node is (0 if direct neighbor) + public var hopsAway: UInt32 { + get {return _hopsAway ?? 0} + set {_hopsAway = newValue} } + /// Returns true if `hopsAway` has been explicitly set. + public var hasHopsAway: Bool {return self._hopsAway != nil} + /// Clears the value of `hopsAway`. Subsequent reads from it will return its default value. + public mutating func clearHopsAway() {self._hopsAway = nil} /// - /// The latest device metrics for the node. - public var deviceMetrics: DeviceMetrics { - get {return _storage._deviceMetrics ?? DeviceMetrics()} - set {_uniqueStorage()._deviceMetrics = newValue} - } - /// Returns true if `deviceMetrics` has been explicitly set. - public var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil} - /// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value. - public mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil} + /// Last byte of the node number of the node that should be used as the next hop to reach this node. + public var nextHop: UInt32 = 0 /// - /// local channel index we heard that node on. Only populated if its not the default channel. - public var channel: UInt32 { - get {return _storage._channel} - set {_uniqueStorage()._channel = newValue} - } + /// Bitfield for storing booleans. See NODEINFO_BITFIELD_* in src/mesh/NodeDB.h. + public var bitfield: UInt32 = 0 /// - /// True if we witnessed the node over MQTT instead of LoRA transport - public var viaMqtt: Bool { - get {return _storage._viaMqtt} - set {_uniqueStorage()._viaMqtt = newValue} - } + /// A full name for this user, i.e. "Kevin Hester". + public var longName: String = String() /// - /// Number of hops away from us this node is (0 if direct neighbor) - public var hopsAway: UInt32 { - get {return _storage._hopsAway ?? 0} - set {_uniqueStorage()._hopsAway = newValue} - } - /// Returns true if `hopsAway` has been explicitly set. - public var hasHopsAway: Bool {return _storage._hopsAway != nil} - /// Clears the value of `hopsAway`. Subsequent reads from it will return its default value. - public mutating func clearHopsAway() {_uniqueStorage()._hopsAway = nil} + /// A VERY short name, ideally two characters or an emoji. + /// Suitable for a tiny OLED screen. + public var shortName: String = String() /// - /// True if node is in our favorites list - /// Persists between NodeDB internal clean ups - public var isFavorite: Bool { - get {return _storage._isFavorite} - set {_uniqueStorage()._isFavorite = newValue} - } + /// Hardware model the user's device is running. + public var hwModel: HardwareModel = .unset /// - /// True if node is in our ignored list - /// Persists between NodeDB internal clean ups - public var isIgnored: Bool { - get {return _storage._isIgnored} - set {_uniqueStorage()._isIgnored = newValue} - } + /// The user's role in the mesh. + public var role: Config.DeviceConfig.Role = .client /// - /// Last byte of the node number of the node that should be used as the next hop to reach this node. - public var nextHop: UInt32 { - get {return _storage._nextHop} - set {_uniqueStorage()._nextHop = newValue} - } + /// The public key of the user's device, for PKI-based encrypted DMs. + public var publicKey: Data = Data() /// - /// Bitfield for storing booleans. - /// LSB 0 is_key_manually_verified - /// LSB 1 is_muted - public var bitfield: UInt32 { - get {return _storage._bitfield} - set {_uniqueStorage()._bitfield = newValue} - } + /// Q4-encoded SNR: dB Γ— 4, sint32 zigzag. Matches RouteDiscovery convention. + /// Encode: snr_q4 = (int32_t)(snr * 4.0f). Decode: snr = snr_q4 / 4.0f. + /// float snr is always zeroed on disk; this field carries all persisted SNR. + public var snrQ4: Int32 = 0 public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} - fileprivate var _storage = _StorageClass.defaultInstance + fileprivate var _hopsAway: UInt32? = nil } /// @@ -353,6 +307,98 @@ public struct DeviceState: @unchecked Sendable { fileprivate var _storage = _StorageClass.defaultInstance } +public struct NodePositionEntry: 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 num: UInt32 = 0 + + public var position: PositionLite { + get {return _position ?? PositionLite()} + set {_position = newValue} + } + /// Returns true if `position` has been explicitly set. + public var hasPosition: Bool {return self._position != nil} + /// Clears the value of `position`. Subsequent reads from it will return its default value. + public mutating func clearPosition() {self._position = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _position: PositionLite? = nil +} + +public struct NodeTelemetryEntry: 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 num: UInt32 = 0 + + public var deviceMetrics: DeviceMetrics { + get {return _deviceMetrics ?? DeviceMetrics()} + set {_deviceMetrics = newValue} + } + /// Returns true if `deviceMetrics` has been explicitly set. + public var hasDeviceMetrics: Bool {return self._deviceMetrics != nil} + /// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value. + public mutating func clearDeviceMetrics() {self._deviceMetrics = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _deviceMetrics: DeviceMetrics? = nil +} + +public struct NodeEnvironmentEntry: 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 num: UInt32 = 0 + + public var environmentMetrics: EnvironmentMetrics { + get {return _environmentMetrics ?? EnvironmentMetrics()} + set {_environmentMetrics = newValue} + } + /// Returns true if `environmentMetrics` has been explicitly set. + public var hasEnvironmentMetrics: Bool {return self._environmentMetrics != nil} + /// Clears the value of `environmentMetrics`. Subsequent reads from it will return its default value. + public mutating func clearEnvironmentMetrics() {self._environmentMetrics = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _environmentMetrics: EnvironmentMetrics? = nil +} + +public struct NodeStatusEntry: 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 num: UInt32 = 0 + + public var status: StatusMessage { + get {return _status ?? StatusMessage()} + set {_status = newValue} + } + /// Returns true if `status` has been explicitly set. + public var hasStatus: Bool {return self._status != nil} + /// Clears the value of `status`. Subsequent reads from it will return its default value. + public mutating func clearStatus() {self._status = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _status: StatusMessage? = nil +} + public struct NodeDatabase: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -368,6 +414,16 @@ public struct NodeDatabase: Sendable { /// New lite version of NodeDB to decrease memory footprint public var nodes: [NodeInfoLite] = [] + /// Per-NodeNum satellite arrays. Constrained platforms (e.g. STM32WL) omit + /// these via MESHTASTIC_EXCLUDE_*DB build flags. + public var positions: [NodePositionEntry] = [] + + public var telemetry: [NodeTelemetryEntry] = [] + + public var status: [NodeStatusEntry] = [] + + public var environment: [NodeEnvironmentEntry] = [] + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -470,7 +526,7 @@ fileprivate let _protobuf_package = "meshtastic" extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".PositionLite" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}latitude_i\0\u{3}longitude_i\0\u{1}altitude\0\u{1}time\0\u{3}location_source\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}latitude_i\0\u{3}longitude_i\0\u{1}altitude\0\u{1}time\0\u{3}location_source\0\u{3}precision_bits\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -483,6 +539,7 @@ extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat case 3: try { try decoder.decodeSingularInt32Field(value: &self.altitude) }() case 4: try { try decoder.decodeSingularFixed32Field(value: &self.time) }() case 5: try { try decoder.decodeSingularEnumField(value: &self.locationSource) }() + case 6: try { try decoder.decodeSingularUInt32Field(value: &self.precisionBits) }() default: break } } @@ -504,6 +561,9 @@ extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if self.locationSource != .locUnset { try visitor.visitSingularEnumField(value: self.locationSource, fieldNumber: 5) } + if self.precisionBits != 0 { + try visitor.visitSingularUInt32Field(value: self.precisionBits, fieldNumber: 6) + } try unknownFields.traverse(visitor: &visitor) } @@ -513,6 +573,7 @@ extension PositionLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if lhs.altitude != rhs.altitude {return false} if lhs.time != rhs.time {return false} if lhs.locationSource != rhs.locationSource {return false} + if lhs.precisionBits != rhs.precisionBits {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -589,153 +650,93 @@ extension UserLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB extension NodeInfoLite: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".NodeInfoLite" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{1}user\0\u{1}position\0\u{1}snr\0\u{3}last_heard\0\u{3}device_metrics\0\u{1}channel\0\u{3}via_mqtt\0\u{3}hops_away\0\u{3}is_favorite\0\u{3}is_ignored\0\u{3}next_hop\0\u{1}bitfield\0") - - fileprivate class _StorageClass { - var _num: UInt32 = 0 - var _user: UserLite? = nil - var _position: PositionLite? = nil - var _snr: Float = 0 - var _lastHeard: UInt32 = 0 - var _deviceMetrics: DeviceMetrics? = nil - var _channel: UInt32 = 0 - var _viaMqtt: Bool = false - var _hopsAway: UInt32? = nil - var _isFavorite: Bool = false - var _isIgnored: Bool = false - var _nextHop: UInt32 = 0 - var _bitfield: UInt32 = 0 - - // This property is used as the initial default value for new instances of the type. - // The type itself is protecting the reference to its storage via CoW semantics. - // This will force a copy to be made of this reference when the first mutation occurs; - // hence, it is safe to mark this as `nonisolated(unsafe)`. - static nonisolated(unsafe) let defaultInstance = _StorageClass() - - private init() {} - - init(copying source: _StorageClass) { - _num = source._num - _user = source._user - _position = source._position - _snr = source._snr - _lastHeard = source._lastHeard - _deviceMetrics = source._deviceMetrics - _channel = source._channel - _viaMqtt = source._viaMqtt - _hopsAway = source._hopsAway - _isFavorite = source._isFavorite - _isIgnored = source._isIgnored - _nextHop = source._nextHop - _bitfield = source._bitfield - } - } - - fileprivate mutating func _uniqueStorage() -> _StorageClass { - if !isKnownUniquelyReferenced(&_storage) { - _storage = _StorageClass(copying: _storage) - } - return _storage - } + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{2}\u{3}snr\0\u{3}last_heard\0\u{2}\u{2}channel\0\u{4}\u{2}hops_away\0\u{4}\u{3}next_hop\0\u{1}bitfield\0\u{3}long_name\0\u{3}short_name\0\u{3}hw_model\0\u{1}role\0\u{3}public_key\0\u{3}snr_q4\0\u{b}user\0\u{b}position\0\u{b}device_metrics\0\u{b}via_mqtt\0\u{b}is_favorite\0\u{b}is_ignored\0\u{c}\u{2}\u{1}\u{c}\u{3}\u{1}\u{c}\u{6}\u{1}\u{c}\u{8}\u{1}\u{c}\u{a}\u{1}\u{c}\u{b}\u{1}") public mutating func decodeMessage(decoder: inout D) throws { - _ = _uniqueStorage() - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularUInt32Field(value: &_storage._num) }() - case 2: try { try decoder.decodeSingularMessageField(value: &_storage._user) }() - case 3: try { try decoder.decodeSingularMessageField(value: &_storage._position) }() - case 4: try { try decoder.decodeSingularFloatField(value: &_storage._snr) }() - case 5: try { try decoder.decodeSingularFixed32Field(value: &_storage._lastHeard) }() - case 6: try { try decoder.decodeSingularMessageField(value: &_storage._deviceMetrics) }() - case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._channel) }() - case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }() - case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }() - case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }() - case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }() - case 12: try { try decoder.decodeSingularUInt32Field(value: &_storage._nextHop) }() - case 13: try { try decoder.decodeSingularUInt32Field(value: &_storage._bitfield) }() - default: break - } + 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.num) }() + case 4: try { try decoder.decodeSingularFloatField(value: &self.snr) }() + case 5: try { try decoder.decodeSingularFixed32Field(value: &self.lastHeard) }() + case 7: try { try decoder.decodeSingularUInt32Field(value: &self.channel) }() + case 9: try { try decoder.decodeSingularUInt32Field(value: &self._hopsAway) }() + case 12: try { try decoder.decodeSingularUInt32Field(value: &self.nextHop) }() + case 13: try { try decoder.decodeSingularUInt32Field(value: &self.bitfield) }() + case 14: try { try decoder.decodeSingularStringField(value: &self.longName) }() + case 15: try { try decoder.decodeSingularStringField(value: &self.shortName) }() + case 16: try { try decoder.decodeSingularEnumField(value: &self.hwModel) }() + case 17: try { try decoder.decodeSingularEnumField(value: &self.role) }() + case 18: try { try decoder.decodeSingularBytesField(value: &self.publicKey) }() + case 19: try { try decoder.decodeSingularSInt32Field(value: &self.snrQ4) }() + default: break } } } public func traverse(visitor: inout V) throws { - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if _storage._num != 0 { - try visitor.visitSingularUInt32Field(value: _storage._num, fieldNumber: 1) - } - try { if let v = _storage._user { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = _storage._position { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if _storage._snr.bitPattern != 0 { - try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) - } - if _storage._lastHeard != 0 { - try visitor.visitSingularFixed32Field(value: _storage._lastHeard, fieldNumber: 5) - } - try { if let v = _storage._deviceMetrics { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - if _storage._channel != 0 { - try visitor.visitSingularUInt32Field(value: _storage._channel, fieldNumber: 7) - } - if _storage._viaMqtt != false { - try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 8) - } - try { if let v = _storage._hopsAway { - try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9) - } }() - if _storage._isFavorite != false { - try visitor.visitSingularBoolField(value: _storage._isFavorite, fieldNumber: 10) - } - if _storage._isIgnored != false { - try visitor.visitSingularBoolField(value: _storage._isIgnored, fieldNumber: 11) - } - if _storage._nextHop != 0 { - try visitor.visitSingularUInt32Field(value: _storage._nextHop, fieldNumber: 12) - } - if _storage._bitfield != 0 { - try visitor.visitSingularUInt32Field(value: _storage._bitfield, fieldNumber: 13) - } + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.num != 0 { + try visitor.visitSingularUInt32Field(value: self.num, fieldNumber: 1) + } + if self.snr.bitPattern != 0 { + try visitor.visitSingularFloatField(value: self.snr, fieldNumber: 4) + } + if self.lastHeard != 0 { + try visitor.visitSingularFixed32Field(value: self.lastHeard, fieldNumber: 5) + } + if self.channel != 0 { + try visitor.visitSingularUInt32Field(value: self.channel, fieldNumber: 7) + } + try { if let v = self._hopsAway { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9) + } }() + if self.nextHop != 0 { + try visitor.visitSingularUInt32Field(value: self.nextHop, fieldNumber: 12) + } + if self.bitfield != 0 { + try visitor.visitSingularUInt32Field(value: self.bitfield, fieldNumber: 13) + } + if !self.longName.isEmpty { + try visitor.visitSingularStringField(value: self.longName, fieldNumber: 14) + } + if !self.shortName.isEmpty { + try visitor.visitSingularStringField(value: self.shortName, fieldNumber: 15) + } + if self.hwModel != .unset { + try visitor.visitSingularEnumField(value: self.hwModel, fieldNumber: 16) + } + if self.role != .client { + try visitor.visitSingularEnumField(value: self.role, fieldNumber: 17) + } + if !self.publicKey.isEmpty { + try visitor.visitSingularBytesField(value: self.publicKey, fieldNumber: 18) + } + if self.snrQ4 != 0 { + try visitor.visitSingularSInt32Field(value: self.snrQ4, fieldNumber: 19) } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: NodeInfoLite, rhs: NodeInfoLite) -> Bool { - if lhs._storage !== rhs._storage { - let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in - let _storage = _args.0 - let rhs_storage = _args.1 - if _storage._num != rhs_storage._num {return false} - if _storage._user != rhs_storage._user {return false} - if _storage._position != rhs_storage._position {return false} - if _storage._snr != rhs_storage._snr {return false} - if _storage._lastHeard != rhs_storage._lastHeard {return false} - if _storage._deviceMetrics != rhs_storage._deviceMetrics {return false} - if _storage._channel != rhs_storage._channel {return false} - if _storage._viaMqtt != rhs_storage._viaMqtt {return false} - if _storage._hopsAway != rhs_storage._hopsAway {return false} - if _storage._isFavorite != rhs_storage._isFavorite {return false} - if _storage._isIgnored != rhs_storage._isIgnored {return false} - if _storage._nextHop != rhs_storage._nextHop {return false} - if _storage._bitfield != rhs_storage._bitfield {return false} - return true - } - if !storagesAreEqual {return false} - } + if lhs.num != rhs.num {return false} + if lhs.snr != rhs.snr {return false} + if lhs.lastHeard != rhs.lastHeard {return false} + if lhs.channel != rhs.channel {return false} + if lhs._hopsAway != rhs._hopsAway {return false} + if lhs.nextHop != rhs.nextHop {return false} + if lhs.bitfield != rhs.bitfield {return false} + if lhs.longName != rhs.longName {return false} + if lhs.shortName != rhs.shortName {return false} + if lhs.hwModel != rhs.hwModel {return false} + if lhs.role != rhs.role {return false} + if lhs.publicKey != rhs.publicKey {return false} + if lhs.snrQ4 != rhs.snrQ4 {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -867,9 +868,165 @@ extension DeviceState: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati } } +extension NodePositionEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodePositionEntry" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{1}position\0") + + 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.num) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._position) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.num != 0 { + try visitor.visitSingularUInt32Field(value: self.num, fieldNumber: 1) + } + try { if let v = self._position { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodePositionEntry, rhs: NodePositionEntry) -> Bool { + if lhs.num != rhs.num {return false} + if lhs._position != rhs._position {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension NodeTelemetryEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodeTelemetryEntry" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{3}device_metrics\0") + + 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.num) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._deviceMetrics) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.num != 0 { + try visitor.visitSingularUInt32Field(value: self.num, fieldNumber: 1) + } + try { if let v = self._deviceMetrics { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodeTelemetryEntry, rhs: NodeTelemetryEntry) -> Bool { + if lhs.num != rhs.num {return false} + if lhs._deviceMetrics != rhs._deviceMetrics {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension NodeEnvironmentEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodeEnvironmentEntry" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{3}environment_metrics\0") + + 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.num) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._environmentMetrics) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.num != 0 { + try visitor.visitSingularUInt32Field(value: self.num, fieldNumber: 1) + } + try { if let v = self._environmentMetrics { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodeEnvironmentEntry, rhs: NodeEnvironmentEntry) -> Bool { + if lhs.num != rhs.num {return false} + if lhs._environmentMetrics != rhs._environmentMetrics {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension NodeStatusEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodeStatusEntry" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{1}status\0") + + 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.num) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._status) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.num != 0 { + try visitor.visitSingularUInt32Field(value: self.num, fieldNumber: 1) + } + try { if let v = self._status { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodeStatusEntry, rhs: NodeStatusEntry) -> Bool { + if lhs.num != rhs.num {return false} + if lhs._status != rhs._status {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension NodeDatabase: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".NodeDatabase" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}version\0\u{1}nodes\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}version\0\u{1}nodes\0\u{1}positions\0\u{1}telemetry\0\u{1}status\0\u{1}environment\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -879,6 +1036,10 @@ extension NodeDatabase: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat switch fieldNumber { case 1: try { try decoder.decodeSingularUInt32Field(value: &self.version) }() case 2: try { try decoder.decodeRepeatedMessageField(value: &self.nodes) }() + case 3: try { try decoder.decodeRepeatedMessageField(value: &self.positions) }() + case 4: try { try decoder.decodeRepeatedMessageField(value: &self.telemetry) }() + case 5: try { try decoder.decodeRepeatedMessageField(value: &self.status) }() + case 6: try { try decoder.decodeRepeatedMessageField(value: &self.environment) }() default: break } } @@ -891,12 +1052,28 @@ extension NodeDatabase: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat if !self.nodes.isEmpty { try visitor.visitRepeatedMessageField(value: self.nodes, fieldNumber: 2) } + if !self.positions.isEmpty { + try visitor.visitRepeatedMessageField(value: self.positions, fieldNumber: 3) + } + if !self.telemetry.isEmpty { + try visitor.visitRepeatedMessageField(value: self.telemetry, fieldNumber: 4) + } + if !self.status.isEmpty { + try visitor.visitRepeatedMessageField(value: self.status, fieldNumber: 5) + } + if !self.environment.isEmpty { + try visitor.visitRepeatedMessageField(value: self.environment, fieldNumber: 6) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: NodeDatabase, rhs: NodeDatabase) -> Bool { if lhs.version != rhs.version {return false} if lhs.nodes != rhs.nodes {return false} + if lhs.positions != rhs.positions {return false} + if lhs.telemetry != rhs.telemetry {return false} + if lhs.status != rhs.status {return false} + if lhs.environment != rhs.environment {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/MeshtasticProtobufs/Sources/meshtastic/deviceonly_legacy.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/deviceonly_legacy.pb.swift new file mode 100644 index 000000000..a1fd4b6a9 --- /dev/null +++ b/MeshtasticProtobufs/Sources/meshtastic/deviceonly_legacy.pb.swift @@ -0,0 +1,333 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// swiftlint:disable all +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: meshtastic/deviceonly_legacy.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +/// +/// Legacy NodeInfoLite descriptor used only to decode pre-split +/// /prefs/nodes.proto saves during the v24 -> v25 migration boot. +/// This preserves the original NodeInfoLite-compatible field numbers needed +/// to parse old wire bytes cleanly, including user (2), position (3), +/// device_metrics (6), and the legacy-only compatibility fields via_mqtt (8), +/// is_favorite (10), and is_ignored (11). Steady-state code does not use +/// this struct; it is dropped after migration completes. This file should be +/// removed once DEVICESTATE_MIN_VER advances past 24. +public struct NodeInfoLite_Legacy: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var num: UInt32 { + get {return _storage._num} + set {_uniqueStorage()._num = newValue} + } + + public var user: UserLite { + get {return _storage._user ?? UserLite()} + set {_uniqueStorage()._user = newValue} + } + /// Returns true if `user` has been explicitly set. + public var hasUser: Bool {return _storage._user != nil} + /// Clears the value of `user`. Subsequent reads from it will return its default value. + public mutating func clearUser() {_uniqueStorage()._user = nil} + + public var position: PositionLite { + get {return _storage._position ?? PositionLite()} + set {_uniqueStorage()._position = newValue} + } + /// Returns true if `position` has been explicitly set. + public var hasPosition: Bool {return _storage._position != nil} + /// Clears the value of `position`. Subsequent reads from it will return its default value. + public mutating func clearPosition() {_uniqueStorage()._position = nil} + + public var snr: Float { + get {return _storage._snr} + set {_uniqueStorage()._snr = newValue} + } + + public var lastHeard: UInt32 { + get {return _storage._lastHeard} + set {_uniqueStorage()._lastHeard = newValue} + } + + public var deviceMetrics: DeviceMetrics { + get {return _storage._deviceMetrics ?? DeviceMetrics()} + set {_uniqueStorage()._deviceMetrics = newValue} + } + /// Returns true if `deviceMetrics` has been explicitly set. + public var hasDeviceMetrics: Bool {return _storage._deviceMetrics != nil} + /// Clears the value of `deviceMetrics`. Subsequent reads from it will return its default value. + public mutating func clearDeviceMetrics() {_uniqueStorage()._deviceMetrics = nil} + + public var channel: UInt32 { + get {return _storage._channel} + set {_uniqueStorage()._channel = newValue} + } + + public var viaMqtt: Bool { + get {return _storage._viaMqtt} + set {_uniqueStorage()._viaMqtt = newValue} + } + + public var hopsAway: UInt32 { + get {return _storage._hopsAway ?? 0} + set {_uniqueStorage()._hopsAway = newValue} + } + /// Returns true if `hopsAway` has been explicitly set. + public var hasHopsAway: Bool {return _storage._hopsAway != nil} + /// Clears the value of `hopsAway`. Subsequent reads from it will return its default value. + public mutating func clearHopsAway() {_uniqueStorage()._hopsAway = nil} + + public var isFavorite: Bool { + get {return _storage._isFavorite} + set {_uniqueStorage()._isFavorite = newValue} + } + + public var isIgnored: Bool { + get {return _storage._isIgnored} + set {_uniqueStorage()._isIgnored = newValue} + } + + public var nextHop: UInt32 { + get {return _storage._nextHop} + set {_uniqueStorage()._nextHop = newValue} + } + + public var bitfield: UInt32 { + get {return _storage._bitfield} + set {_uniqueStorage()._bitfield = newValue} + } + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance +} + +/// +/// Legacy NodeDatabase shape: one repeated array of fat NodeInfoLite_Legacy +/// with no satellite position/telemetry arrays. +public struct NodeDatabase_Legacy: 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 version: UInt32 = 0 + + public var nodes: [NodeInfoLite_Legacy] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "meshtastic" + +extension NodeInfoLite_Legacy: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodeInfoLite_Legacy" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{1}user\0\u{1}position\0\u{1}snr\0\u{3}last_heard\0\u{3}device_metrics\0\u{1}channel\0\u{3}via_mqtt\0\u{3}hops_away\0\u{3}is_favorite\0\u{3}is_ignored\0\u{3}next_hop\0\u{1}bitfield\0") + + fileprivate class _StorageClass { + var _num: UInt32 = 0 + var _user: UserLite? = nil + var _position: PositionLite? = nil + var _snr: Float = 0 + var _lastHeard: UInt32 = 0 + var _deviceMetrics: DeviceMetrics? = nil + var _channel: UInt32 = 0 + var _viaMqtt: Bool = false + var _hopsAway: UInt32? = nil + var _isFavorite: Bool = false + var _isIgnored: Bool = false + var _nextHop: UInt32 = 0 + var _bitfield: UInt32 = 0 + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _num = source._num + _user = source._user + _position = source._position + _snr = source._snr + _lastHeard = source._lastHeard + _deviceMetrics = source._deviceMetrics + _channel = source._channel + _viaMqtt = source._viaMqtt + _hopsAway = source._hopsAway + _isFavorite = source._isFavorite + _isIgnored = source._isIgnored + _nextHop = source._nextHop + _bitfield = source._bitfield + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &_storage._num) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._user) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._position) }() + case 4: try { try decoder.decodeSingularFloatField(value: &_storage._snr) }() + case 5: try { try decoder.decodeSingularFixed32Field(value: &_storage._lastHeard) }() + case 6: try { try decoder.decodeSingularMessageField(value: &_storage._deviceMetrics) }() + case 7: try { try decoder.decodeSingularUInt32Field(value: &_storage._channel) }() + case 8: try { try decoder.decodeSingularBoolField(value: &_storage._viaMqtt) }() + case 9: try { try decoder.decodeSingularUInt32Field(value: &_storage._hopsAway) }() + case 10: try { try decoder.decodeSingularBoolField(value: &_storage._isFavorite) }() + case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }() + case 12: try { try decoder.decodeSingularUInt32Field(value: &_storage._nextHop) }() + case 13: try { try decoder.decodeSingularUInt32Field(value: &_storage._bitfield) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if _storage._num != 0 { + try visitor.visitSingularUInt32Field(value: _storage._num, fieldNumber: 1) + } + try { if let v = _storage._user { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._position { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if _storage._snr.bitPattern != 0 { + try visitor.visitSingularFloatField(value: _storage._snr, fieldNumber: 4) + } + if _storage._lastHeard != 0 { + try visitor.visitSingularFixed32Field(value: _storage._lastHeard, fieldNumber: 5) + } + try { if let v = _storage._deviceMetrics { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() + if _storage._channel != 0 { + try visitor.visitSingularUInt32Field(value: _storage._channel, fieldNumber: 7) + } + if _storage._viaMqtt != false { + try visitor.visitSingularBoolField(value: _storage._viaMqtt, fieldNumber: 8) + } + try { if let v = _storage._hopsAway { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9) + } }() + if _storage._isFavorite != false { + try visitor.visitSingularBoolField(value: _storage._isFavorite, fieldNumber: 10) + } + if _storage._isIgnored != false { + try visitor.visitSingularBoolField(value: _storage._isIgnored, fieldNumber: 11) + } + if _storage._nextHop != 0 { + try visitor.visitSingularUInt32Field(value: _storage._nextHop, fieldNumber: 12) + } + if _storage._bitfield != 0 { + try visitor.visitSingularUInt32Field(value: _storage._bitfield, fieldNumber: 13) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodeInfoLite_Legacy, rhs: NodeInfoLite_Legacy) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._num != rhs_storage._num {return false} + if _storage._user != rhs_storage._user {return false} + if _storage._position != rhs_storage._position {return false} + if _storage._snr != rhs_storage._snr {return false} + if _storage._lastHeard != rhs_storage._lastHeard {return false} + if _storage._deviceMetrics != rhs_storage._deviceMetrics {return false} + if _storage._channel != rhs_storage._channel {return false} + if _storage._viaMqtt != rhs_storage._viaMqtt {return false} + if _storage._hopsAway != rhs_storage._hopsAway {return false} + if _storage._isFavorite != rhs_storage._isFavorite {return false} + if _storage._isIgnored != rhs_storage._isIgnored {return false} + if _storage._nextHop != rhs_storage._nextHop {return false} + if _storage._bitfield != rhs_storage._bitfield {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension NodeDatabase_Legacy: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".NodeDatabase_Legacy" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}version\0\u{1}nodes\0") + + 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.version) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.nodes) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.version != 0 { + try visitor.visitSingularUInt32Field(value: self.version, fieldNumber: 1) + } + if !self.nodes.isEmpty { + try visitor.visitRepeatedMessageField(value: self.nodes, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: NodeDatabase_Legacy, rhs: NodeDatabase_Legacy) -> Bool { + if lhs.version != rhs.version {return false} + if lhs.nodes != rhs.nodes {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift index 9f338c605..3b7da95d7 100644 --- a/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift +++ b/MeshtasticProtobufs/Sources/meshtastic/mesh.pb.swift @@ -594,6 +594,22 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { /// Lilygo T-Echo Card case tEchoCard // = 136 + /// + /// Seeed Tracker L2 + case seeedWioTrackerL2 // = 137 + + /// + /// Elecrow CrowPanel Advance P4 models, ESP32-P4 and TFT with SX1262 radio plugin + case crowpanelP4 // = 138 + + /// + /// Heltec Mesh Tower V2 + case heltecMeshTowerV2 // = 139 + + /// + /// Meshnology W10 + case meshnologyW10 // = 140 + /// /// ------------------------------------------------------------------------------------------------------------------------------------------ /// 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. @@ -744,6 +760,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { case 134: self = .stationG3 case 135: self = .tImpulsePlus case 136: self = .tEchoCard + case 137: self = .seeedWioTrackerL2 + case 138: self = .crowpanelP4 + case 139: self = .heltecMeshTowerV2 + case 140: self = .meshnologyW10 case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -888,6 +908,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { case .stationG3: return 134 case .tImpulsePlus: return 135 case .tEchoCard: return 136 + case .seeedWioTrackerL2: return 137 + case .crowpanelP4: return 138 + case .heltecMeshTowerV2: return 139 + case .meshnologyW10: return 140 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -1032,6 +1056,10 @@ public enum HardwareModel: SwiftProtobuf.Enum, Swift.CaseIterable { .stationG3, .tImpulsePlus, .tEchoCard, + .seeedWioTrackerL2, + .crowpanelP4, + .heltecMeshTowerV2, + .meshnologyW10, .privateHw, ] @@ -1813,6 +1841,9 @@ public struct User: Sendable { /// /// A full name for this user, i.e. "Kevin Hester" + /// Limited to 24 bytes of UTF-8: longer names are accepted from senders + /// built against the older 39-byte limit, but devices truncate them before + /// storing or rebroadcasting. Clients should enforce 24 bytes in their UI. public var longName: String = String() /// @@ -2176,6 +2207,10 @@ public struct DataMessage: Sendable { /// Clears the value of `bitfield`. Subsequent reads from it will return its default value. public mutating func clearBitfield() {self._bitfield = nil} + /// + /// XEdDSA signature for the payload + public var xeddsaSignature: Data = Data() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -2807,6 +2842,13 @@ public struct MeshPacket: @unchecked Sendable { set {_uniqueStorage()._transportMechanism = newValue} } + /// + /// Indicates whether the packet has a valid signature + public var xeddsaSigned: Bool { + get {return _storage._xeddsaSigned} + set {_uniqueStorage()._xeddsaSigned = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public enum OneOf_PayloadVariant: Equatable, Sendable { @@ -3023,6 +3065,10 @@ public struct MeshPacket: @unchecked Sendable { /// /// Arrived via API connection case transportApi // = 7 + + /// + /// Arrived via Unicast UDP + case transportUnicastUdp // = 8 case UNRECOGNIZED(Int) public init() { @@ -3039,6 +3085,7 @@ public struct MeshPacket: @unchecked Sendable { case 5: self = .transportMqtt case 6: self = .transportMulticastUdp case 7: self = .transportApi + case 8: self = .transportUnicastUdp default: self = .UNRECOGNIZED(rawValue) } } @@ -3053,6 +3100,7 @@ public struct MeshPacket: @unchecked Sendable { case .transportMqtt: return 5 case .transportMulticastUdp: return 6 case .transportApi: return 7 + case .transportUnicastUdp: return 8 case .UNRECOGNIZED(let i): return i } } @@ -3067,6 +3115,7 @@ public struct MeshPacket: @unchecked Sendable { .transportMqtt, .transportMulticastUdp, .transportApi, + .transportUnicastUdp, ] } @@ -3212,6 +3261,15 @@ public struct NodeInfo: @unchecked Sendable { set {_uniqueStorage()._isMuted = newValue} } + /// + /// True if node is signing its packets via XEdDSA + /// Persists between NodeDB internal clean ups + /// LSB 1 of the bitfield + public var hasXeddsaSigned_p: Bool { + get {return _storage._hasXeddsaSigned_p} + set {_uniqueStorage()._hasXeddsaSigned_p = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -3597,6 +3655,20 @@ public struct FromRadio: Sendable { set {payloadVariant = .lockdownStatus(newValue)} } + /// + /// Map of which modem presets are legal in each LoRa region. Sent once + /// during the want_config handshake (right after `metadata`, before the + /// first `channel`) so client UIs can prevent the user from selecting an + /// illegal region+preset combination. A region that does not appear in + /// any group carries no constraint info and should not be restricted. + public var regionPresets: LoRaRegionPresetMap { + get { + if case .regionPresets(let v)? = payloadVariant {return v} + return LoRaRegionPresetMap() + } + set {payloadVariant = .regionPresets(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -3665,6 +3737,13 @@ public struct FromRadio: Sendable { /// to report success or failure. Replaces the earlier scheme of /// encoding state as magic-string prefixes inside ClientNotification. case lockdownStatus(LockdownStatus) + /// + /// Map of which modem presets are legal in each LoRa region. Sent once + /// during the want_config handshake (right after `metadata`, before the + /// first `channel`) so client UIs can prevent the user from selecting an + /// illegal region+preset combination. A region that does not appear in + /// any group carries no constraint info and should not be restricted. + case regionPresets(LoRaRegionPresetMap) } @@ -3744,6 +3823,15 @@ public struct LockdownStatus: Sendable { /// /// Passphrase rejected. backoff_seconds is non-zero when rate-limited. case unlockFailed // = 4 + + /// + /// Lockdown is supported by this firmware but not currently active + /// (no passphrase has been provisioned, or it was disabled via + /// AdminMessage.lockdown_auth.disable). The device is operating in + /// normal, non-encrypted mode. Clients render the lockdown-mode + /// toggle as OFF on receiving this. Distinct from NEEDS_PROVISION, + /// which is only used during an in-progress enable flow. + case disabled // = 5 case UNRECOGNIZED(Int) public init() { @@ -3757,6 +3845,7 @@ public struct LockdownStatus: Sendable { case 2: self = .locked case 3: self = .unlocked case 4: self = .unlockFailed + case 5: self = .disabled default: self = .UNRECOGNIZED(rawValue) } } @@ -3768,6 +3857,7 @@ public struct LockdownStatus: Sendable { case .locked: return 2 case .unlocked: return 3 case .unlockFailed: return 4 + case .disabled: return 5 case .UNRECOGNIZED(let i): return i } } @@ -3779,6 +3869,7 @@ public struct LockdownStatus: Sendable { .locked, .unlocked, .unlockFailed, + .disabled, ] } @@ -4214,6 +4305,92 @@ public struct DeviceMetadata: Sendable { /// (bitwise OR of ExcludedModules) public var excludedModules: UInt32 = 0 + /// + /// Indicates that the device has a buzzer or audio output usable by the find-node command + public var hasBuzzer_p: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// +/// A distinct set of legal modem presets shared by one or more LoRa regions. +/// Regions that have an identical preset list / default / licensing reference +/// the same group (by index) via LoRaRegionPresetMap.region_groups. This keeps +/// the whole map small enough to fit in a single FromRadio packet, since most +/// regions share the one standard preset list. +public struct LoRaPresetGroup: 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 modem presets that are legal for every region referencing this group. + public var presets: [Config.LoRaConfig.ModemPreset] = [] + + /// + /// The firmware's default modem preset for regions in this group. + /// Always one of `presets`. Clients should select this when switching to one + /// of these regions, or when the current preset is not legal in the new region. + public var defaultPreset: Config.LoRaConfig.ModemPreset = .longFast + + /// + /// True if regions referencing this group are for licensed operators only + /// (e.g. amateur / ham radio bands). Clients should warn or gate accordingly. + public var licensedOnly: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// +/// Associates a single LoRa region with its preset group. +public struct LoRaRegionPresets: 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 LoRa region this entry describes. + public var region: Config.LoRaConfig.RegionCode = .unset + + /// + /// Index into LoRaRegionPresetMap.groups for the preset list that is legal + /// in `region`. + public var groupIndex: UInt32 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +/// +/// Map describing which modem presets are valid for each LoRa region. Sent by +/// the firmware during the want_config handshake (as FromRadio.region_presets) +/// so that client UIs can prevent illegal region+preset selections. +/// +/// Delivery is grouped to save space: `groups` holds each distinct preset list, +/// and `region_groups` maps every known region to one of those groups by index. +/// A region that does NOT appear in `region_groups` carries no constraint +/// information and should not be restricted by the client (e.g. firmware that +/// predates this message, or a region with no firmware table entry). Clients +/// must also tolerate this whole message being absent. +public struct LoRaRegionPresetMap: 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. + + /// + /// One entry per distinct (preset-list, default, licensing) combination. + /// Referenced by index from `region_groups`. + public var groups: [LoRaPresetGroup] = [] + + /// + /// One entry per known LoRa region, pointing at its preset group. + public var regionGroups: [LoRaRegionPresets] = [] + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -4371,7 +4548,7 @@ public struct ChunkedPayloadResponse: Sendable { fileprivate let _protobuf_package = "meshtastic" extension HardwareModel: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSET\0\u{1}TLORA_V2\0\u{1}TLORA_V1\0\u{1}TLORA_V2_1_1P6\0\u{1}TBEAM\0\u{1}HELTEC_V2_0\0\u{1}TBEAM_V0P7\0\u{1}T_ECHO\0\u{1}TLORA_V1_1P3\0\u{1}RAK4631\0\u{1}HELTEC_V2_1\0\u{1}HELTEC_V1\0\u{1}LILYGO_TBEAM_S3_CORE\0\u{1}RAK11200\0\u{1}NANO_G1\0\u{1}TLORA_V2_1_1P8\0\u{1}TLORA_T3_S3\0\u{1}NANO_G1_EXPLORER\0\u{1}NANO_G2_ULTRA\0\u{1}LORA_TYPE\0\u{1}WIPHONE\0\u{1}WIO_WM1110\0\u{1}RAK2560\0\u{1}HELTEC_HRU_3601\0\u{1}HELTEC_WIRELESS_BRIDGE\0\u{1}STATION_G1\0\u{1}RAK11310\0\u{1}SENSELORA_RP2040\0\u{1}SENSELORA_S3\0\u{1}CANARYONE\0\u{1}RP2040_LORA\0\u{1}STATION_G2\0\u{1}LORA_RELAY_V1\0\u{1}T_ECHO_PLUS\0\u{1}PPR\0\u{1}GENIEBLOCKS\0\u{1}NRF52_UNKNOWN\0\u{1}PORTDUINO\0\u{1}ANDROID_SIM\0\u{1}DIY_V1\0\u{1}NRF52840_PCA10059\0\u{1}DR_DEV\0\u{1}M5STACK\0\u{1}HELTEC_V3\0\u{1}HELTEC_WSL_V3\0\u{1}BETAFPV_2400_TX\0\u{1}BETAFPV_900_NANO_TX\0\u{1}RPI_PICO\0\u{1}HELTEC_WIRELESS_TRACKER\0\u{1}HELTEC_WIRELESS_PAPER\0\u{1}T_DECK\0\u{1}T_WATCH_S3\0\u{1}PICOMPUTER_S3\0\u{1}HELTEC_HT62\0\u{1}EBYTE_ESP32_S3\0\u{1}ESP32_S3_PICO\0\u{1}CHATTER_2\0\u{1}HELTEC_WIRELESS_PAPER_V1_0\0\u{1}HELTEC_WIRELESS_TRACKER_V1_0\0\u{1}UNPHONE\0\u{1}TD_LORAC\0\u{1}CDEBYTE_EORA_S3\0\u{1}TWC_MESH_V4\0\u{1}NRF52_PROMICRO_DIY\0\u{1}RADIOMASTER_900_BANDIT_NANO\0\u{1}HELTEC_CAPSULE_SENSOR_V3\0\u{1}HELTEC_VISION_MASTER_T190\0\u{1}HELTEC_VISION_MASTER_E213\0\u{1}HELTEC_VISION_MASTER_E290\0\u{1}HELTEC_MESH_NODE_T114\0\u{1}SENSECAP_INDICATOR\0\u{1}TRACKER_T1000_E\0\u{1}RAK3172\0\u{1}WIO_E5\0\u{1}RADIOMASTER_900_BANDIT\0\u{1}ME25LS01_4Y10TD\0\u{1}RP2040_FEATHER_RFM95\0\u{1}M5STACK_COREBASIC\0\u{1}M5STACK_CORE2\0\u{1}RPI_PICO2\0\u{1}M5STACK_CORES3\0\u{1}SEEED_XIAO_S3\0\u{1}MS24SF1\0\u{1}TLORA_C6\0\u{1}WISMESH_TAP\0\u{1}ROUTASTIC\0\u{1}MESH_TAB\0\u{1}MESHLINK\0\u{1}XIAO_NRF52_KIT\0\u{1}THINKNODE_M1\0\u{1}THINKNODE_M2\0\u{1}T_ETH_ELITE\0\u{1}HELTEC_SENSOR_HUB\0\u{1}MUZI_BASE\0\u{1}HELTEC_MESH_POCKET\0\u{1}SEEED_SOLAR_NODE\0\u{1}NOMADSTAR_METEOR_PRO\0\u{1}CROWPANEL\0\u{1}LINK_32\0\u{1}SEEED_WIO_TRACKER_L1\0\u{1}SEEED_WIO_TRACKER_L1_EINK\0\u{1}MUZI_R1_NEO\0\u{1}T_DECK_PRO\0\u{1}T_LORA_PAGER\0\u{1}M5STACK_RESERVED\0\u{1}WISMESH_TAG\0\u{1}RAK3312\0\u{1}THINKNODE_M5\0\u{1}HELTEC_MESH_SOLAR\0\u{1}T_ECHO_LITE\0\u{1}HELTEC_V4\0\u{1}M5STACK_C6L\0\u{1}M5STACK_CARDPUTER_ADV\0\u{1}HELTEC_WIRELESS_TRACKER_V2\0\u{1}T_WATCH_ULTRA\0\u{1}THINKNODE_M3\0\u{1}WISMESH_TAP_V2\0\u{1}RAK3401\0\u{1}RAK6421\0\u{1}THINKNODE_M4\0\u{1}THINKNODE_M6\0\u{1}MESHSTICK_1262\0\u{1}TBEAM_1_WATT\0\u{1}T5_S3_EPAPER_PRO\0\u{1}TBEAM_BPF\0\u{1}MINI_EPAPER_S3\0\u{1}TDISPLAY_S3_PRO\0\u{1}HELTEC_MESH_NODE_T096\0\u{1}TRACKER_T1000_E_PRO\0\u{1}THINKNODE_M7\0\u{1}THINKNODE_M8\0\u{1}THINKNODE_M9\0\u{1}HELTEC_V4_R8\0\u{1}HELTEC_MESH_NODE_T1\0\u{1}STATION_G3\0\u{1}T_IMPULSE_PLUS\0\u{1}T_ECHO_CARD\0\u{2}w\u{1}PRIVATE_HW\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSET\0\u{1}TLORA_V2\0\u{1}TLORA_V1\0\u{1}TLORA_V2_1_1P6\0\u{1}TBEAM\0\u{1}HELTEC_V2_0\0\u{1}TBEAM_V0P7\0\u{1}T_ECHO\0\u{1}TLORA_V1_1P3\0\u{1}RAK4631\0\u{1}HELTEC_V2_1\0\u{1}HELTEC_V1\0\u{1}LILYGO_TBEAM_S3_CORE\0\u{1}RAK11200\0\u{1}NANO_G1\0\u{1}TLORA_V2_1_1P8\0\u{1}TLORA_T3_S3\0\u{1}NANO_G1_EXPLORER\0\u{1}NANO_G2_ULTRA\0\u{1}LORA_TYPE\0\u{1}WIPHONE\0\u{1}WIO_WM1110\0\u{1}RAK2560\0\u{1}HELTEC_HRU_3601\0\u{1}HELTEC_WIRELESS_BRIDGE\0\u{1}STATION_G1\0\u{1}RAK11310\0\u{1}SENSELORA_RP2040\0\u{1}SENSELORA_S3\0\u{1}CANARYONE\0\u{1}RP2040_LORA\0\u{1}STATION_G2\0\u{1}LORA_RELAY_V1\0\u{1}T_ECHO_PLUS\0\u{1}PPR\0\u{1}GENIEBLOCKS\0\u{1}NRF52_UNKNOWN\0\u{1}PORTDUINO\0\u{1}ANDROID_SIM\0\u{1}DIY_V1\0\u{1}NRF52840_PCA10059\0\u{1}DR_DEV\0\u{1}M5STACK\0\u{1}HELTEC_V3\0\u{1}HELTEC_WSL_V3\0\u{1}BETAFPV_2400_TX\0\u{1}BETAFPV_900_NANO_TX\0\u{1}RPI_PICO\0\u{1}HELTEC_WIRELESS_TRACKER\0\u{1}HELTEC_WIRELESS_PAPER\0\u{1}T_DECK\0\u{1}T_WATCH_S3\0\u{1}PICOMPUTER_S3\0\u{1}HELTEC_HT62\0\u{1}EBYTE_ESP32_S3\0\u{1}ESP32_S3_PICO\0\u{1}CHATTER_2\0\u{1}HELTEC_WIRELESS_PAPER_V1_0\0\u{1}HELTEC_WIRELESS_TRACKER_V1_0\0\u{1}UNPHONE\0\u{1}TD_LORAC\0\u{1}CDEBYTE_EORA_S3\0\u{1}TWC_MESH_V4\0\u{1}NRF52_PROMICRO_DIY\0\u{1}RADIOMASTER_900_BANDIT_NANO\0\u{1}HELTEC_CAPSULE_SENSOR_V3\0\u{1}HELTEC_VISION_MASTER_T190\0\u{1}HELTEC_VISION_MASTER_E213\0\u{1}HELTEC_VISION_MASTER_E290\0\u{1}HELTEC_MESH_NODE_T114\0\u{1}SENSECAP_INDICATOR\0\u{1}TRACKER_T1000_E\0\u{1}RAK3172\0\u{1}WIO_E5\0\u{1}RADIOMASTER_900_BANDIT\0\u{1}ME25LS01_4Y10TD\0\u{1}RP2040_FEATHER_RFM95\0\u{1}M5STACK_COREBASIC\0\u{1}M5STACK_CORE2\0\u{1}RPI_PICO2\0\u{1}M5STACK_CORES3\0\u{1}SEEED_XIAO_S3\0\u{1}MS24SF1\0\u{1}TLORA_C6\0\u{1}WISMESH_TAP\0\u{1}ROUTASTIC\0\u{1}MESH_TAB\0\u{1}MESHLINK\0\u{1}XIAO_NRF52_KIT\0\u{1}THINKNODE_M1\0\u{1}THINKNODE_M2\0\u{1}T_ETH_ELITE\0\u{1}HELTEC_SENSOR_HUB\0\u{1}MUZI_BASE\0\u{1}HELTEC_MESH_POCKET\0\u{1}SEEED_SOLAR_NODE\0\u{1}NOMADSTAR_METEOR_PRO\0\u{1}CROWPANEL\0\u{1}LINK_32\0\u{1}SEEED_WIO_TRACKER_L1\0\u{1}SEEED_WIO_TRACKER_L1_EINK\0\u{1}MUZI_R1_NEO\0\u{1}T_DECK_PRO\0\u{1}T_LORA_PAGER\0\u{1}M5STACK_RESERVED\0\u{1}WISMESH_TAG\0\u{1}RAK3312\0\u{1}THINKNODE_M5\0\u{1}HELTEC_MESH_SOLAR\0\u{1}T_ECHO_LITE\0\u{1}HELTEC_V4\0\u{1}M5STACK_C6L\0\u{1}M5STACK_CARDPUTER_ADV\0\u{1}HELTEC_WIRELESS_TRACKER_V2\0\u{1}T_WATCH_ULTRA\0\u{1}THINKNODE_M3\0\u{1}WISMESH_TAP_V2\0\u{1}RAK3401\0\u{1}RAK6421\0\u{1}THINKNODE_M4\0\u{1}THINKNODE_M6\0\u{1}MESHSTICK_1262\0\u{1}TBEAM_1_WATT\0\u{1}T5_S3_EPAPER_PRO\0\u{1}TBEAM_BPF\0\u{1}MINI_EPAPER_S3\0\u{1}TDISPLAY_S3_PRO\0\u{1}HELTEC_MESH_NODE_T096\0\u{1}TRACKER_T1000_E_PRO\0\u{1}THINKNODE_M7\0\u{1}THINKNODE_M8\0\u{1}THINKNODE_M9\0\u{1}HELTEC_V4_R8\0\u{1}HELTEC_MESH_NODE_T1\0\u{1}STATION_G3\0\u{1}T_IMPULSE_PLUS\0\u{1}T_ECHO_CARD\0\u{1}SEEED_WIO_TRACKER_L2\0\u{1}CROWPANEL_P4\0\u{1}HELTEC_MESH_TOWER_V2\0\u{1}MESHNOLOGY_W10\0\u{2}s\u{1}PRIVATE_HW\0") } extension Constants: SwiftProtobuf._ProtoNameProviding { @@ -4826,7 +5003,7 @@ extension Routing.Error: SwiftProtobuf._ProtoNameProviding { extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".Data" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}portnum\0\u{1}payload\0\u{3}want_response\0\u{1}dest\0\u{1}source\0\u{3}request_id\0\u{3}reply_id\0\u{1}emoji\0\u{1}bitfield\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}portnum\0\u{1}payload\0\u{3}want_response\0\u{1}dest\0\u{1}source\0\u{3}request_id\0\u{3}reply_id\0\u{1}emoji\0\u{1}bitfield\0\u{3}xeddsa_signature\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4843,6 +5020,7 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati case 7: try { try decoder.decodeSingularFixed32Field(value: &self.replyID) }() case 8: try { try decoder.decodeSingularFixed32Field(value: &self.emoji) }() case 9: try { try decoder.decodeSingularUInt32Field(value: &self._bitfield) }() + case 10: try { try decoder.decodeSingularBytesField(value: &self.xeddsaSignature) }() default: break } } @@ -4880,6 +5058,9 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati try { if let v = self._bitfield { try visitor.visitSingularUInt32Field(value: v, fieldNumber: 9) } }() + if !self.xeddsaSignature.isEmpty { + try visitor.visitSingularBytesField(value: self.xeddsaSignature, fieldNumber: 10) + } try unknownFields.traverse(visitor: &visitor) } @@ -4893,6 +5074,7 @@ extension DataMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati if lhs.replyID != rhs.replyID {return false} if lhs.emoji != rhs.emoji {return false} if lhs._bitfield != rhs._bitfield {return false} + if lhs.xeddsaSignature != rhs.xeddsaSignature {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -5264,7 +5446,7 @@ extension MqttClientProxyMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageI extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".MeshPacket" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}from\0\u{1}to\0\u{1}channel\0\u{1}decoded\0\u{1}encrypted\0\u{1}id\0\u{3}rx_time\0\u{3}rx_snr\0\u{3}hop_limit\0\u{3}want_ack\0\u{1}priority\0\u{3}rx_rssi\0\u{1}delayed\0\u{3}via_mqtt\0\u{3}hop_start\0\u{3}public_key\0\u{3}pki_encrypted\0\u{3}next_hop\0\u{3}relay_node\0\u{3}tx_after\0\u{3}transport_mechanism\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}from\0\u{1}to\0\u{1}channel\0\u{1}decoded\0\u{1}encrypted\0\u{1}id\0\u{3}rx_time\0\u{3}rx_snr\0\u{3}hop_limit\0\u{3}want_ack\0\u{1}priority\0\u{3}rx_rssi\0\u{1}delayed\0\u{3}via_mqtt\0\u{3}hop_start\0\u{3}public_key\0\u{3}pki_encrypted\0\u{3}next_hop\0\u{3}relay_node\0\u{3}tx_after\0\u{3}transport_mechanism\0\u{3}xeddsa_signed\0") fileprivate class _StorageClass { var _from: UInt32 = 0 @@ -5287,6 +5469,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio var _relayNode: UInt32 = 0 var _txAfter: UInt32 = 0 var _transportMechanism: MeshPacket.TransportMechanism = .transportInternal + var _xeddsaSigned: Bool = false // This property is used as the initial default value for new instances of the type. // The type itself is protecting the reference to its storage via CoW semantics. @@ -5317,6 +5500,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio _relayNode = source._relayNode _txAfter = source._txAfter _transportMechanism = source._transportMechanism + _xeddsaSigned = source._xeddsaSigned } } @@ -5375,6 +5559,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio case 19: try { try decoder.decodeSingularUInt32Field(value: &_storage._relayNode) }() case 20: try { try decoder.decodeSingularUInt32Field(value: &_storage._txAfter) }() case 21: try { try decoder.decodeSingularEnumField(value: &_storage._transportMechanism) }() + case 22: try { try decoder.decodeSingularBoolField(value: &_storage._xeddsaSigned) }() default: break } } @@ -5455,6 +5640,9 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._transportMechanism != .transportInternal { try visitor.visitSingularEnumField(value: _storage._transportMechanism, fieldNumber: 21) } + if _storage._xeddsaSigned != false { + try visitor.visitSingularBoolField(value: _storage._xeddsaSigned, fieldNumber: 22) + } } try unknownFields.traverse(visitor: &visitor) } @@ -5484,6 +5672,7 @@ extension MeshPacket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio if _storage._relayNode != rhs_storage._relayNode {return false} if _storage._txAfter != rhs_storage._txAfter {return false} if _storage._transportMechanism != rhs_storage._transportMechanism {return false} + if _storage._xeddsaSigned != rhs_storage._xeddsaSigned {return false} return true } if !storagesAreEqual {return false} @@ -5502,12 +5691,12 @@ extension MeshPacket.Delayed: SwiftProtobuf._ProtoNameProviding { } extension MeshPacket.TransportMechanism: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0TRANSPORT_INTERNAL\0\u{1}TRANSPORT_LORA\0\u{1}TRANSPORT_LORA_ALT1\0\u{1}TRANSPORT_LORA_ALT2\0\u{1}TRANSPORT_LORA_ALT3\0\u{1}TRANSPORT_MQTT\0\u{1}TRANSPORT_MULTICAST_UDP\0\u{1}TRANSPORT_API\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0TRANSPORT_INTERNAL\0\u{1}TRANSPORT_LORA\0\u{1}TRANSPORT_LORA_ALT1\0\u{1}TRANSPORT_LORA_ALT2\0\u{1}TRANSPORT_LORA_ALT3\0\u{1}TRANSPORT_MQTT\0\u{1}TRANSPORT_MULTICAST_UDP\0\u{1}TRANSPORT_API\0\u{1}TRANSPORT_UNICAST_UDP\0") } extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".NodeInfo" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{1}user\0\u{1}position\0\u{1}snr\0\u{3}last_heard\0\u{3}device_metrics\0\u{1}channel\0\u{3}via_mqtt\0\u{3}hops_away\0\u{3}is_favorite\0\u{3}is_ignored\0\u{3}is_key_manually_verified\0\u{3}is_muted\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}num\0\u{1}user\0\u{1}position\0\u{1}snr\0\u{3}last_heard\0\u{3}device_metrics\0\u{1}channel\0\u{3}via_mqtt\0\u{3}hops_away\0\u{3}is_favorite\0\u{3}is_ignored\0\u{3}is_key_manually_verified\0\u{3}is_muted\0\u{3}has_xeddsa_signed\0") fileprivate class _StorageClass { var _num: UInt32 = 0 @@ -5523,6 +5712,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB var _isIgnored: Bool = false var _isKeyManuallyVerified: Bool = false var _isMuted: Bool = false + var _hasXeddsaSigned_p: Bool = false // This property is used as the initial default value for new instances of the type. // The type itself is protecting the reference to its storage via CoW semantics. @@ -5546,6 +5736,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB _isIgnored = source._isIgnored _isKeyManuallyVerified = source._isKeyManuallyVerified _isMuted = source._isMuted + _hasXeddsaSigned_p = source._hasXeddsaSigned_p } } @@ -5577,6 +5768,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB case 11: try { try decoder.decodeSingularBoolField(value: &_storage._isIgnored) }() case 12: try { try decoder.decodeSingularBoolField(value: &_storage._isKeyManuallyVerified) }() case 13: try { try decoder.decodeSingularBoolField(value: &_storage._isMuted) }() + case 14: try { try decoder.decodeSingularBoolField(value: &_storage._hasXeddsaSigned_p) }() default: break } } @@ -5628,6 +5820,9 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if _storage._isMuted != false { try visitor.visitSingularBoolField(value: _storage._isMuted, fieldNumber: 13) } + if _storage._hasXeddsaSigned_p != false { + try visitor.visitSingularBoolField(value: _storage._hasXeddsaSigned_p, fieldNumber: 14) + } } try unknownFields.traverse(visitor: &visitor) } @@ -5650,6 +5845,7 @@ extension NodeInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB if _storage._isIgnored != rhs_storage._isIgnored {return false} if _storage._isKeyManuallyVerified != rhs_storage._isKeyManuallyVerified {return false} if _storage._isMuted != rhs_storage._isMuted {return false} + if _storage._hasXeddsaSigned_p != rhs_storage._hasXeddsaSigned_p {return false} return true } if !storagesAreEqual {return false} @@ -5815,7 +6011,7 @@ extension QueueStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementati extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".FromRadio" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}packet\0\u{3}my_info\0\u{3}node_info\0\u{1}config\0\u{3}log_record\0\u{3}config_complete_id\0\u{1}rebooted\0\u{1}moduleConfig\0\u{1}channel\0\u{1}queueStatus\0\u{1}xmodemPacket\0\u{1}metadata\0\u{1}mqttClientProxyMessage\0\u{1}fileInfo\0\u{1}clientNotification\0\u{1}deviceuiConfig\0\u{3}lockdown_status\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{1}packet\0\u{3}my_info\0\u{3}node_info\0\u{1}config\0\u{3}log_record\0\u{3}config_complete_id\0\u{1}rebooted\0\u{1}moduleConfig\0\u{1}channel\0\u{1}queueStatus\0\u{1}xmodemPacket\0\u{1}metadata\0\u{1}mqttClientProxyMessage\0\u{1}fileInfo\0\u{1}clientNotification\0\u{1}deviceuiConfig\0\u{3}lockdown_status\0\u{3}region_presets\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -6035,6 +6231,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation self.payloadVariant = .lockdownStatus(v) } }() + case 19: try { + var v: LoRaRegionPresetMap? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .regionPresets(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .regionPresets(v) + } + }() default: break } } @@ -6117,6 +6326,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation guard case .lockdownStatus(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 18) }() + case .regionPresets?: try { + guard case .regionPresets(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 19) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -6181,7 +6394,7 @@ extension LockdownStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement } extension LockdownStatus.State: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0STATE_UNSPECIFIED\0\u{1}NEEDS_PROVISION\0\u{1}LOCKED\0\u{1}UNLOCKED\0\u{1}UNLOCK_FAILED\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0STATE_UNSPECIFIED\0\u{1}NEEDS_PROVISION\0\u{1}LOCKED\0\u{1}UNLOCKED\0\u{1}UNLOCK_FAILED\0\u{1}DISABLED\0") } extension ClientNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { @@ -6767,7 +6980,7 @@ extension Neighbor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".DeviceMetadata" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}firmware_version\0\u{3}device_state_version\0\u{1}canShutdown\0\u{1}hasWifi\0\u{1}hasBluetooth\0\u{1}hasEthernet\0\u{1}role\0\u{3}position_flags\0\u{3}hw_model\0\u{1}hasRemoteHardware\0\u{1}hasPKC\0\u{3}excluded_modules\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}firmware_version\0\u{3}device_state_version\0\u{1}canShutdown\0\u{1}hasWifi\0\u{1}hasBluetooth\0\u{1}hasEthernet\0\u{1}role\0\u{3}position_flags\0\u{3}hw_model\0\u{1}hasRemoteHardware\0\u{1}hasPKC\0\u{3}excluded_modules\0\u{1}hasBuzzer\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -6787,6 +7000,7 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement case 10: try { try decoder.decodeSingularBoolField(value: &self.hasRemoteHardware_p) }() case 11: try { try decoder.decodeSingularBoolField(value: &self.hasPkc_p) }() case 12: try { try decoder.decodeSingularUInt32Field(value: &self.excludedModules) }() + case 13: try { try decoder.decodeSingularBoolField(value: &self.hasBuzzer_p) }() default: break } } @@ -6829,6 +7043,9 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if self.excludedModules != 0 { try visitor.visitSingularUInt32Field(value: self.excludedModules, fieldNumber: 12) } + if self.hasBuzzer_p != false { + try visitor.visitSingularBoolField(value: self.hasBuzzer_p, fieldNumber: 13) + } try unknownFields.traverse(visitor: &visitor) } @@ -6845,6 +7062,117 @@ extension DeviceMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplement if lhs.hasRemoteHardware_p != rhs.hasRemoteHardware_p {return false} if lhs.hasPkc_p != rhs.hasPkc_p {return false} if lhs.excludedModules != rhs.excludedModules {return false} + if lhs.hasBuzzer_p != rhs.hasBuzzer_p {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension LoRaPresetGroup: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LoRaPresetGroup" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}presets\0\u{3}default_preset\0\u{3}licensed_only\0") + + 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.decodeRepeatedEnumField(value: &self.presets) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self.defaultPreset) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.licensedOnly) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.presets.isEmpty { + try visitor.visitPackedEnumField(value: self.presets, fieldNumber: 1) + } + if self.defaultPreset != .longFast { + try visitor.visitSingularEnumField(value: self.defaultPreset, fieldNumber: 2) + } + if self.licensedOnly != false { + try visitor.visitSingularBoolField(value: self.licensedOnly, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: LoRaPresetGroup, rhs: LoRaPresetGroup) -> Bool { + if lhs.presets != rhs.presets {return false} + if lhs.defaultPreset != rhs.defaultPreset {return false} + if lhs.licensedOnly != rhs.licensedOnly {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension LoRaRegionPresets: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LoRaRegionPresets" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}region\0\u{3}group_index\0") + + 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.decodeSingularEnumField(value: &self.region) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.groupIndex) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.region != .unset { + try visitor.visitSingularEnumField(value: self.region, fieldNumber: 1) + } + if self.groupIndex != 0 { + try visitor.visitSingularUInt32Field(value: self.groupIndex, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: LoRaRegionPresets, rhs: LoRaRegionPresets) -> Bool { + if lhs.region != rhs.region {return false} + if lhs.groupIndex != rhs.groupIndex {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension LoRaRegionPresetMap: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".LoRaRegionPresetMap" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}groups\0\u{3}region_groups\0") + + 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.decodeRepeatedMessageField(value: &self.groups) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.regionGroups) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.groups.isEmpty { + try visitor.visitRepeatedMessageField(value: self.groups, fieldNumber: 1) + } + if !self.regionGroups.isEmpty { + try visitor.visitRepeatedMessageField(value: self.regionGroups, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: LoRaRegionPresetMap, rhs: LoRaRegionPresetMap) -> Bool { + if lhs.groups != rhs.groups {return false} + if lhs.regionGroups != rhs.regionGroups {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/protobufs b/protobufs index dd6c3f850..6566cd618 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit dd6c3f850a56a00cbc2cc5b93db2fd43b1a2f46a +Subproject commit 6566cd61812af44c5a3e77f01db8f72de0a1020a