diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 68489033..5049c74e 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -409,7 +409,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } let receivingNode = fetchedNodes.first(where: { $0.num == destNum }) let connectedNode = fetchedNodes.first(where: { $0.num == self.connectedPeripheral.num }) - traceRoute.id = Int64(meshPacket.id) traceRoute.time = Date() traceRoute.node = receivingNode @@ -524,7 +523,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var nowKnown = false // MyInfo from initial connection - if decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 { + if context != nil && decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 { let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, peripheralId: self.connectedPeripheral.id, context: context!) if myInfo != nil { @@ -660,6 +659,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let traceRouteHop = TraceRouteHopEntity(context: context!) traceRouteHop.time = Date() if hopNode?.hasPositions ?? false { + traceRoute?.hasPositions = true let mostRecent = hopNode?.positions?.lastObject as! PositionEntity if mostRecent.time! >= Calendar.current.date(byAdding: .minute, value: -60, to: Date())! { traceRouteHop.altitude = mostRecent.altitude diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index c25edc2f..dc1e16b6 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -162,6 +162,7 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo if newChannel.name?.lowercased() == "admin" { fetchedMyInfo[0].adminIndex = newChannel.index } + context.refresh(newChannel, mergeChanges: true) do { try context.save() } catch { diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift index e21351cb..1196e1b5 100644 --- a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -33,7 +33,7 @@ class MqttClientProxyManager { } else if host != nil && host!.contains(":") { if let fullHost = host { host = fullHost.components(separatedBy: ":")[0] - defaultServerPort = Int(fullHost.components(separatedBy: ":")[1]) ?? 1883 + defaultServerPort = Int(fullHost.components(separatedBy: ":")[1]) ?? (useSsl ? 8883 : 1883) } } if let host = host { diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index ba84dafc..e1e87b6a 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -114,10 +114,10 @@ struct DeviceMetricsLog: View { } else { ScrollView { let columns = [ - GridItem(.flexible(minimum: 30, maximum: 45), spacing: 0.1), - GridItem(.flexible(minimum: 30, maximum: 50), spacing: 0.1), - GridItem(.flexible(minimum: 30, maximum: 70), spacing: 0.1), - GridItem(.flexible(minimum: 30, maximum: 65), spacing: 0.1), + GridItem(.flexible(minimum: 35, maximum: 55), spacing: 0.1), + GridItem(.flexible(minimum: 35, maximum: 60), spacing: 0.1), + GridItem(.flexible(minimum: 35, maximum: 70), spacing: 0.1), + GridItem(.flexible(minimum: 35, maximum: 65), spacing: 0.1), GridItem(.flexible(minimum: 130, maximum: 200), spacing: 0.1) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 33fca131..d71a4bd1 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -43,7 +43,6 @@ struct Channels: View { var body: some View { VStack { - List { if #available(iOS 17.0, macOS 14.0, *) { TipView(CreateChannelsTip(), arrowEdge: .bottom) @@ -98,16 +97,18 @@ struct Channels: View { if node?.myInfo?.channels?.array.count ?? 0 < 8 && node != nil { Button { + let lastChannel = node?.myInfo?.channels?.lastObject as? ChannelEntity + var lastChannelIndex = lastChannel?.index ?? 0 channelKeySize = 16 let key = generateChannelKey(size: channelKeySize) channelName = "" - channelIndex = Int32(node!.myInfo!.channels!.array.count) + channelIndex = Int32(lastChannelIndex + 1) channelRole = 2 channelKey = key uplink = false downlink = false - hasChanges = false + hasChanges = true isPresentingEditView = true } label: { @@ -270,11 +271,38 @@ struct Channels: View { channel.index = channelIndex channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary if channel.role != Channel.Role.disabled { - channel.settings.id = UInt32(channelIndex) + channel.index = channelIndex channel.settings.name = channelName channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() channel.settings.uplinkEnabled = uplink channel.settings.downlinkEnabled = downlink + + let newChannel = ChannelEntity(context: context) + newChannel.id = Int32(channel.index) + newChannel.index = Int32(channel.index) + newChannel.uplinkEnabled = channel.settings.uplinkEnabled + newChannel.downlinkEnabled = channel.settings.downlinkEnabled + newChannel.name = channel.settings.name + newChannel.role = Int32(channel.role.rawValue) + newChannel.psk = channel.settings.psk + guard let mutableChannels = node?.myInfo?.channels?.mutableCopy() as? NSMutableOrderedSet else { + return + } + if mutableChannels.contains(newChannel) { + mutableChannels.replaceObject(at: Int(newChannel.index), with: newChannel) + } else { + mutableChannels.add(newChannel) + } + node!.myInfo!.channels = mutableChannels.copy() as? NSOrderedSet + context.refresh(newChannel, mergeChanges: true) + do { + try context.save() + print("💾 Saved Channel: \(channel.settings.name)") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)") + } } else { if channelIndex <= node!.myInfo!.channels?.count ?? 0 { guard let channelEntity = node!.myInfo!.channels?[Int(channelIndex)] as? ChannelEntity else { @@ -297,6 +325,7 @@ struct Channels: View { if adminMessageId > 0 { self.isPresentingEditView = false channelName = "" + channelRole = 2 hasChanges = false _ = bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index bff7ca00..a5bb46f9 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -81,46 +81,72 @@ struct Settings: View { let node = nodes.first(where: { $0.num == preferredNodeNum }) let hasAdmin = node?.myInfo?.adminIndex ?? 0 > 0 ? true : false + + if !(node?.deviceConfig?.isManaged ?? false) { - Section("Configure") { - if hasAdmin { - Picker("Configuring Node", selection: $selectedNode) { - if selectedNode == 0 { - Text("Connect to a Node").tag(0) - } - ForEach(nodes) { node in - if node.num == bleManager.connectedPeripheral?.num ?? 0 { - Text("BLE Config: \(node.user?.longName ?? "unknown".localized)") - .tag(Int(node.num)) - } else if node.metadata != nil { - Text("Remote Config: \(node.user?.longName ?? "unknown".localized)") - .tag(Int(node.num)) - } else if hasAdmin { - Text("Request Admin: \(node.user?.longName ?? "unknown".localized)") - .tag(Int(node.num)) + if bleManager.connectedPeripheral != nil { + Section("Configure") { + if hasAdmin { + Picker("Configuring Node", selection: $selectedNode) { + if selectedNode == 0 { + Text("Connect to a Node").tag(0) } - } - } - .pickerStyle(.automatic) - .labelsHidden() - .onChange(of: selectedNode) { newValue in - if selectedNode > 0 { - let node = nodes.first(where: { $0.num == newValue }) - let connectedNode = nodes.first(where: { $0.num == preferredNodeNum }) - preferredNodeNum = Int(connectedNode?.num ?? 0)// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) - if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil { - let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) - if adminMessageId > 0 { - print("Sent node metadata request from node details") + ForEach(nodes) { node in + if node.num == bleManager.connectedPeripheral?.num ?? 0 { + Text("BLE Config: \(node.user?.longName ?? "unknown".localized)") + .tag(Int(node.num)) + } else if node.metadata != nil { + Text("Remote Config: \(node.user?.longName ?? "unknown".localized)") + .tag(Int(node.num)) + } else if hasAdmin { + Text("Request Admin: \(node.user?.longName ?? "unknown".localized)") + .tag(Int(node.num)) } } } + .pickerStyle(.automatic) + .labelsHidden() + .onChange(of: selectedNode) { newValue in + if selectedNode > 0 { + let node = nodes.first(where: { $0.num == newValue }) + let connectedNode = nodes.first(where: { $0.num == preferredNodeNum }) + preferredNodeNum = Int(connectedNode?.num ?? 0)// Int(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral?.num ?? 0 : 0) + if connectedNode != nil && connectedNode?.user != nil && connectedNode?.myInfo != nil && node?.user != nil && node?.metadata == nil { + let adminMessageId = bleManager.requestDeviceMetadata(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode!.myInfo!.adminIndex, context: context) + if adminMessageId > 0 { + print("Sent node metadata request from node details") + } + } + } + } + } else { + if bleManager.connectedPeripheral != nil { + Text("Connected Node \(node?.user?.longName ?? "unknown".localized)") + } } - } else { - Text("Connected Node \(node?.user?.longName ?? "unknown".localized)") } } Section("radio.configuration") { + if node != nil && node?.loRaConfig != nil { + let rc = RegionCodes(rawValue: Int(node?.loRaConfig?.regionCode ?? 0)) + if rc?.dutyCycle ?? 0 < 1 { + + Label { + Text("Hourly Duty Cycle") + } icon: { + Image(systemName: "clock.arrow.circlepath") + .symbolRenderingMode(.hierarchical) + .foregroundColor(.red) + } + Text("Your region has a \(rc?.dutyCycle ?? 0)% hourly duty cycle, your radio will stop sending packets when it reaches the hourly limit.") + .foregroundColor(.orange) + .font(.caption) + Text("Limit all periodic broadcasts intervals especially telemetry and position. If you need to increase hops, do it on nodes at the edges, not the ones in the middle. MQTT is not advised when you are duty cycle restricted because the gateway node is then doing all the work.") + .font(.caption2) + .foregroundColor(.gray) + + } + } NavigationLink { LoRaConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: {