diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 4d2e9cf9..8bef46b3 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; }; DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */; }; DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */; }; + DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */; }; DD4033C228B286B70096A444 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4033C128B286B70096A444 /* Onboarding.swift */; }; DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582528582E9B009B0E59 /* DeviceConfig.swift */; }; DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; }; @@ -140,6 +141,7 @@ DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = ""; }; DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = ""; }; DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingEnums.swift; sourceTree = ""; }; + DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCoreData.swift; sourceTree = ""; }; DD4033C128B286B70096A444 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = ""; }; DD41582528582E9B009B0E59 /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = ""; }; DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = ""; }; @@ -267,12 +269,12 @@ DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( + DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */, + DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */, + DD2E65252767A01F00E45FC5 /* NodeDetail.swift */, DD47E3CD26F103C600029299 /* NodeList.swift */, DD90860D26F69BAE00DC5189 /* NodeMap.swift */, - DD2E65252767A01F00E45FC5 /* NodeDetail.swift */, DD73FD1028750779000852D6 /* PositionLog.swift */, - DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */, - DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */, ); path = Nodes; sourceTree = ""; @@ -519,6 +521,7 @@ DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */, DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */, DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */, + DD3CC6C128EB9D4900FA9159 /* UpdateCoreData.swift */, ); path = Helpers; sourceTree = ""; @@ -723,6 +726,7 @@ DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */, DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, + DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */, DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */, DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */, DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */, diff --git a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6685f05b..bccfe8c9 100644 --- a/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Meshtastic.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,25 +1,23 @@ { - "object": { - "pins": [ - { - "package": "SQLite.swift", - "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", - "state": { - "branch": null, - "revision": "60a65015f6402b7c34b9a924f755ca0a73afeeaa", - "version": "0.13.1" - } - }, - { - "package": "SwiftProtobuf", - "repositoryURL": "https://github.com/apple/swift-protobuf.git", - "state": { - "branch": null, - "revision": "e1499bc69b9040b29184f7f2996f7bab467c1639", - "version": "1.19.0" - } + "pins" : [ + { + "identity" : "sqlite.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stephencelis/SQLite.swift.git", + "state" : { + "revision" : "60a65015f6402b7c34b9a924f755ca0a73afeeaa", + "version" : "0.13.1" } - ] - }, - "version": 1 + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639", + "version" : "1.19.0" + } + } + ], + "version" : 2 } diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index e6a8fa66..b03411dd 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -76,7 +76,7 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { csvString += "\n" csvString += String(pos.seqNo) csvString += ", " - csvString += String(pos.latitude ?? 0) + csvString += String((pos.latitude ?? 0)) csvString += ", " csvString += String(pos.longitude ?? 0) csvString += ", " diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 425a9822..815b5502 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -621,13 +621,16 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph nowKnown = true localConfig(config: decodedInfo.config, meshlogging: meshLoggingEnabled, context: context!, nodeNum: self.connectedPeripheral.num, nodeLongName: self.connectedPeripheral.longName) - } // Module Config if decodedInfo.moduleConfig.isInitialized && !invalidVersion { nowKnown = true moduleConfig(config: decodedInfo.moduleConfig, meshlogging: meshLoggingEnabled, context: context!, nodeNum: self.connectedPeripheral.num, nodeLongName: self.connectedPeripheral.longName) + + if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) { + self.getCannedMessageModuleMessages(destNum: self.connectedPeripheral.num, wantResponse: true) + } } // Log any other unknownApp calls if !nowKnown { @@ -656,7 +659,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph routingPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) } case .adminApp: - adminAppPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) + adminAppPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) case .replyApp: if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())") } case .ipTunnelApp: @@ -681,10 +684,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Private App UNHANDLED \(try! decodedInfo.packet.jsonString())") } case .atakForwarder: if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for ATAK Forwarder App UNHANDLED \(try! decodedInfo.packet.jsonString())") } + case .simulatorApp: + if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Simulator App UNHANDLED \(try! decodedInfo.packet.jsonString())") } case .UNRECOGNIZED(_): if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") } case .max: print("MAX PORT NUM OF 511") + } // MARK: Check for an All / Broadcast User @@ -1134,22 +1140,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { - do { - - try context!.save() - - if meshLoggingEnabled { MeshLogger.log("💾 Saved a Factory Reset Admin Message for node: \(String(destNum))") } - + if meshLoggingEnabled { MeshLogger.log("💾 Sent a Factory Reset for node: \(String(destNum))") } connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true - - } catch { - - context!.rollback() - - let nsError = error as NSError - print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") - } } return false @@ -1182,23 +1175,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { do { - - try context!.save() - - if meshLoggingEnabled { MeshLogger.log("💾 Sent a NodeDB Reset Admin Message for node: \(String(destNum))") } - + if meshLoggingEnabled { MeshLogger.log("💾 Sent a NodeDB Reset for node: \(String(destNum))") } connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - - PersistenceController.shared.clearDatabase() - return true - } catch { - - context!.rollback() - - let nsError = error as NSError - print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") + print("💥 Error Sending NodeDB Reset") } } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index cf6f6a14..0a22b190 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1077,63 +1077,69 @@ func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManage func adminAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { if let channelMessage = try? Channel(serializedData: packet.decoded.payload) { - - let fetchedMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") - fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(packet.from)) - do { + if channelMessage.hasSettings { - let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) as! [MyInfoEntity] - - if fetchedMyInfo.count == 1 { + let fetchedMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(packet.from)) + + do { - // Update - if fetchedMyInfo[0].channels?.count ?? 0 >= 1 { + let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) as! [MyInfoEntity] + + if fetchedMyInfo.count == 1 { - let newChannel = ChannelEntity(context: context) - newChannel.index = Int32(channelMessage.settings.channelNum) - newChannel.uplinkEnabled = channelMessage.settings.uplinkEnabled - newChannel.downlinkEnabled = channelMessage.settings.downlinkEnabled - newChannel.name = channelMessage.settings.name - newChannel.role = Int32(channelMessage.role.rawValue) - - let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as! NSMutableOrderedSet - - mutableChannels.add(newChannel) - fetchedMyInfo[0].channels = mutableChannels.copy() as? NSOrderedSet + // Update + if fetchedMyInfo[0].channels?.count ?? 0 >= 1 { + + let newChannel = ChannelEntity(context: context) + newChannel.index = Int32(channelMessage.settings.channelNum) + newChannel.uplinkEnabled = channelMessage.settings.uplinkEnabled + newChannel.downlinkEnabled = channelMessage.settings.downlinkEnabled + newChannel.name = channelMessage.settings.name + newChannel.role = Int32(channelMessage.role.rawValue) + + let mutableChannels = fetchedMyInfo[0].channels!.mutableCopy() as! NSMutableOrderedSet + + mutableChannels.add(newChannel) + fetchedMyInfo[0].channels = mutableChannels.copy() as? NSOrderedSet + + } else { + + let newChannel = ChannelEntity(context: context) + newChannel.index = Int32(channelMessage.settings.channelNum) + newChannel.uplinkEnabled = channelMessage.settings.uplinkEnabled + newChannel.downlinkEnabled = channelMessage.settings.downlinkEnabled + newChannel.name = channelMessage.settings.name + newChannel.role = Int32(channelMessage.role.rawValue) + + var newChannels = [ChannelEntity]() + newChannels.append(newChannel) + fetchedMyInfo[0].channels! = NSOrderedSet(array: newChannels) + } } else { - - let newChannel = ChannelEntity(context: context) - newChannel.index = Int32(channelMessage.settings.channelNum) - newChannel.uplinkEnabled = channelMessage.settings.uplinkEnabled - newChannel.downlinkEnabled = channelMessage.settings.downlinkEnabled - newChannel.name = channelMessage.settings.name - newChannel.role = Int32(channelMessage.role.rawValue) - - var newChannels = [ChannelEntity]() - newChannels.append(newChannel) - fetchedMyInfo[0].channels! = NSOrderedSet(array: newChannels) + print("💥 Trying to save a channel to a MyInfo that does not exist: \(packet.from)") } - } else { - print("💥 Trying to save a channel to a MyInfo that does not exist: \(packet.from)") - } - - try context.save() - - if meshLogging { + try context.save() - MeshLogger.log("💾 Updated MyInfo channel \(channelMessage.settings.channelNum) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)") + if meshLogging { + + MeshLogger.log("💾 Updated MyInfo channel \(channelMessage.settings.channelNum) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)") + } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError)") } - - } catch { - - context.rollback() - - let nsError = error as NSError - print("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError)") } + } else { + + print(try! packet.decoded.jsonString()) } } diff --git a/Meshtastic/Helpers/UpdateCoreData.swift b/Meshtastic/Helpers/UpdateCoreData.swift new file mode 100644 index 00000000..5a5d71e3 --- /dev/null +++ b/Meshtastic/Helpers/UpdateCoreData.swift @@ -0,0 +1,81 @@ +// +// UpdateCoreData.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 10/3/22. + +import CoreData + +public func clearPositions(destNum: Int64, context: NSManagedObjectContext) -> Bool { + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + + let newPostions = [PositionEntity]() + fetchedNode[0].positions? = NSOrderedSet(array: newPostions) + + do { + try context.save() + return true + + } catch { + context.rollback() + return false + } + + } catch { + print("💥 Fetch NodeInfoEntity Error") + return false + } +} + +public func clearTelemetry(destNum: Int64, metricsType: Int32, context: NSManagedObjectContext) -> Bool { + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(destNum)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + + let emptyTelemetry = [TelemetryEntity]() + fetchedNode[0].telemetries? = NSOrderedSet(array: emptyTelemetry) + + do { + try context.save() + return true + + } catch { + context.rollback() + return false + } + + } catch { + print("💥 Fetch NodeInfoEntity Error") + return false + } +} + +public func clearCoreDataDatabase(context: NSManagedObjectContext) { + + let persistenceController = PersistenceController.shared.container + + for i in 0...persistenceController.managedObjectModel.entities.count-1 { + let entity = persistenceController.managedObjectModel.entities[i] + + do { + let query = NSFetchRequest(entityName: entity.name!) + let deleterequest = NSBatchDeleteRequest(fetchRequest: query) + try context.execute(deleterequest) + try context.save() + + } catch let error as NSError { + print("Error: \(error.localizedDescription)") + abort() + } + } +} diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents index b59226c5..a34a3211 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents @@ -58,6 +58,7 @@ + diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index c37af6e4..49025b20 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -34,7 +34,6 @@ class PersistenceController { init(inMemory: Bool = false) { container = NSPersistentContainer(name: "Meshtastic") - //self.clearDatabase() if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") @@ -63,10 +62,10 @@ class PersistenceController { try persistentStoreCoordinator.destroyPersistentStore(at:url, ofType: NSSQLiteStoreType, options: nil) try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) - print("💥 Something went terribly wrong, CoreData database truncated. All app data is lost.") + print("💥 CoreData database truncated. All app data has been erased.") } catch let error { - print("💣 Failed to destroy broken CoreData database, delete the app. Attempted to clear persistent store: " + error.localizedDescription) + print("💣 Failed to destroy CoreData database, delete the app and re-install to clear data. Attempted to clear persistent store: " + error.localizedDescription) } } } diff --git a/Meshtastic/Protobufs/channel.pb.swift b/Meshtastic/Protobufs/channel.pb.swift index e859ba7a..219dcb6c 100644 --- a/Meshtastic/Protobufs/channel.pb.swift +++ b/Meshtastic/Protobufs/channel.pb.swift @@ -46,24 +46,7 @@ struct ChannelSettings { // methods supported on all messages. /// - /// NOTE: this field is _independent_ and unrelated to the concepts in channel.proto. - /// this is controlling the actual hardware frequency the radio is transmitting on. - /// In a perfect world we would have called it something else (band?) but I forgot to make this change during the big 1.2 renaming. - /// Most users should never need to be exposed to this field/concept. - /// A channel number between 1 and 13 (or whatever the max is in the current - /// region). If ZERO then the rule is "use the old channel name hash based - /// algorithm to derive the channel number") - /// If using the hash algorithm the channel number will be: hash(channel_name) % - /// NUM_CHANNELS (Where num channels depends on the regulatory region). - /// NUM_CHANNELS_US is 13, for other values see MeshRadio.h in the device code. - /// hash a string into an integer - djb2 by Dan Bernstein. - - /// http://www.cse.yorku.ca/~oz/hash.html - /// unsigned long hash(char *str) { - /// unsigned long hash = 5381; int c; - /// while ((c = *str++) != 0) - /// hash = ((hash << 5) + hash) + (unsigned char) c; - /// return hash; - /// } + /// Deprecated in favor of LoraConfig.channel_num var channelNum: UInt32 = 0 /// diff --git a/Meshtastic/Protobufs/config.pb.swift b/Meshtastic/Protobufs/config.pb.swift index 663bca2b..b7215ece 100644 --- a/Meshtastic/Protobufs/config.pb.swift +++ b/Meshtastic/Protobufs/config.pb.swift @@ -705,6 +705,16 @@ struct Config { /// Units are in dBm. var txPower: Int32 = 0 + /// + /// This is controlling the actual hardware frequency the radio is transmitting on. + /// Most users should never need to be exposed to this field/concept. + /// A channel number between 1 and NUM_CHANNELS (whatever the max is in the current region). + /// If ZERO then the rule is "use the old channel name hash based + /// algorithm to derive the channel number") + /// If using the hash algorithm the channel number will be: hash(channel_name) % + /// NUM_CHANNELS (Where num channels depends on the regulatory region). + var channelNum: UInt32 = 0 + /// /// For testing it is useful sometimes to force a node to never listen to /// particular other nodes (simulating radio out of range). All nodenums listed @@ -1599,6 +1609,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 8: .standard(proto: "hop_limit"), 9: .standard(proto: "tx_enabled"), 10: .standard(proto: "tx_power"), + 11: .standard(proto: "channel_num"), 103: .standard(proto: "ignore_incoming"), ] @@ -1618,6 +1629,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem case 8: try { try decoder.decodeSingularUInt32Field(value: &self.hopLimit) }() case 9: try { try decoder.decodeSingularBoolField(value: &self.txEnabled) }() case 10: try { try decoder.decodeSingularInt32Field(value: &self.txPower) }() + case 11: try { try decoder.decodeSingularUInt32Field(value: &self.channelNum) }() case 103: try { try decoder.decodeRepeatedUInt32Field(value: &self.ignoreIncoming) }() default: break } @@ -1655,6 +1667,9 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if self.txPower != 0 { try visitor.visitSingularInt32Field(value: self.txPower, fieldNumber: 10) } + if self.channelNum != 0 { + try visitor.visitSingularUInt32Field(value: self.channelNum, fieldNumber: 11) + } if !self.ignoreIncoming.isEmpty { try visitor.visitPackedUInt32Field(value: self.ignoreIncoming, fieldNumber: 103) } @@ -1672,6 +1687,7 @@ extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if lhs.hopLimit != rhs.hopLimit {return false} if lhs.txEnabled != rhs.txEnabled {return false} if lhs.txPower != rhs.txPower {return false} + if lhs.channelNum != rhs.channelNum {return false} if lhs.ignoreIncoming != rhs.ignoreIncoming {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true diff --git a/Meshtastic/Protobufs/portnums.pb.swift b/Meshtastic/Protobufs/portnums.pb.swift index 1f0ae3a2..efeea5d4 100644 --- a/Meshtastic/Protobufs/portnums.pb.swift +++ b/Meshtastic/Protobufs/portnums.pb.swift @@ -119,6 +119,13 @@ enum PortNum: SwiftProtobuf.Enum { /// Project files at https://github.com/a-f-G-U-C/Meshtastic-ZPS case zpsApp // = 68 + /// + /// Used to let multiple instances of Linux native applications communicate + /// as if they did using their LoRa chip. + /// Maintained by GitHub user GUVWAF. + /// Project files at https://github.com/GUVWAF/Meshtasticator + case simulatorApp // = 69 + /// /// Private applications should use portnums >= 256. /// To simplify initial development and testing you can use "PRIVATE_APP" @@ -156,6 +163,7 @@ enum PortNum: SwiftProtobuf.Enum { case 66: self = .rangeTestApp case 67: self = .telemetryApp case 68: self = .zpsApp + case 69: self = .simulatorApp case 256: self = .privateApp case 257: self = .atakForwarder case 511: self = .max @@ -181,6 +189,7 @@ enum PortNum: SwiftProtobuf.Enum { case .rangeTestApp: return 66 case .telemetryApp: return 67 case .zpsApp: return 68 + case .simulatorApp: return 69 case .privateApp: return 256 case .atakForwarder: return 257 case .max: return 511 @@ -211,6 +220,7 @@ extension PortNum: CaseIterable { .rangeTestApp, .telemetryApp, .zpsApp, + .simulatorApp, .privateApp, .atakForwarder, .max, @@ -243,6 +253,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding { 66: .same(proto: "RANGE_TEST_APP"), 67: .same(proto: "TELEMETRY_APP"), 68: .same(proto: "ZPS_APP"), + 69: .same(proto: "SIMULATOR_APP"), 256: .same(proto: "PRIVATE_APP"), 257: .same(proto: "ATAK_FORWARDER"), 511: .same(proto: "MAX"), diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 1a554947..b10711d0 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -131,7 +131,7 @@ struct Connect: View { Text("Bitrate: \(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00))") Text("Ch. Utilization: \(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00))") Text("Air Time: \(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00))") - Text("RSSI: \(bleManager.connectedPeripheral.rssi)") + Text("BLE RSSI: \(bleManager.connectedPeripheral.rssi)") } } else { diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 77c6dfd9..14290db2 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -12,6 +12,8 @@ struct DeviceMetricsLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @State private var isPresentingClearLogConfirm: Bool = false + @State var isExporting = false @State var exportString = "" @@ -105,19 +107,53 @@ struct DeviceMetricsLog: View { .padding(.trailing, 5) } } - Button { + HStack { + + Button(role: .destructive) { + + isPresentingClearLogConfirm = true + + } label: { + + Label("Clear Log", systemImage: "trash.fill") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingClearLogConfirm, + titleVisibility: .visible + ) { + Button("Delete all device metrics?", role: .destructive) { + + if clearTelemetry(destNum: node.num, metricsType: 0, context: context) { - exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) - isExporting = true + print("Clear Device Metrics Log Failed") + + } else { + + + } + } + } - } label: { - - Label("Save", systemImage: "square.and.arrow.down") + Button { + + exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() + .navigationTitle("Device Metrics Log") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 6b17cbbc..3283a01d 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -11,6 +11,8 @@ struct EnvironmentMetricsLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @State private var isPresentingClearLogConfirm: Bool = false + @State var isExporting = false @State var exportString = "" @@ -79,19 +81,50 @@ struct EnvironmentMetricsLog: View { .padding(.trailing, 5) } } - Button { + + HStack { + + Button(role: .destructive) { + + isPresentingClearLogConfirm = true + + } label: { + + Label("Clear Log", systemImage: "trash.fill") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingClearLogConfirm, + titleVisibility: .visible + ) { + Button("Delete all environment metrics?", role: .destructive) { + + if clearTelemetry(destNum: node.num, metricsType: 1, context: context) { - exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 1) - isExporting = true + print("Clear Environment Metrics Log Failed") + + } + } + } - } label: { - - Label("Save", systemImage: "square.and.arrow.down") + Button { + + exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 1) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() .navigationTitle("Environment Metrics Log") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 43d9eb7e..fd3e1c17 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -326,8 +326,6 @@ struct NodeDetail: View { if (node.positions?.count ?? 0) > 0 { - - NavigationLink { PositionLog(node: node) } label: { @@ -336,7 +334,7 @@ struct NodeDetail: View { .symbolRenderingMode(.hierarchical) .font(.title) - Text("Position Log (\(node.positions?.count ?? 0) Points)") + Text("Position Log") .font(.title3) } .fixedSize(horizontal: false, vertical: true) diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 9602514d..25072e80 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -15,6 +15,8 @@ struct PositionLog: View { @State var exportString = "" var node: NodeInfoEntity + + @State private var isPresentingClearLogConfirm = false var body: some View { @@ -70,38 +72,71 @@ struct PositionLog: View { .padding(.leading, 15) .padding(.trailing, 5) } - Button { - - exportString = PositionToCsvFile(positions: node.positions!.array as! [PositionEntity]) - isExporting = true - - } label: { - - Label("Save", systemImage: "square.and.arrow.down") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - } - .fileExporter( - isPresented: $isExporting, - document: CsvDocument(emptyCsv: exportString), - contentType: .commaSeparatedText, - defaultFilename: String("\(node.user?.longName ?? "Node") Position Log"), - onCompletion: { result in + HStack { - if case .success = result { + Button(role: .destructive) { + + isPresentingClearLogConfirm = true - print("Position log download succeeded.") - self.isExporting = false + } label: { - } else { - - print("Position log download failed: \(result).") + Label("Clear Log", systemImage: "trash.fill") } - } - ) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingClearLogConfirm, + titleVisibility: .visible + ) { + Button("Delete all positions?", role: .destructive) { + + if clearPositions(destNum: node.num, context: context) { + + print("Clear Position Log Failed") + + } else { + + + } + } + } + + Button { + + exportString = PositionToCsvFile(positions: node.positions!.array as! [PositionEntity]) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user?.longName ?? "Node") Position Log"), + onCompletion: { result in + + if case .success = result { + + print("Position log download succeeded.") + self.isExporting = false + + } else { + + print("Position log download failed: \(result).") + } + } + ) + } .navigationTitle("Position Log \(node.positions?.count ?? 0) Points") .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 86911931..0b5f200f 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -13,6 +13,7 @@ struct DeviceConfig: View { var node: NodeInfoEntity? + @State private var isPresentingNodeDBResetConfirm = false @State private var isPresentingFactoryResetConfirm = false @State private var isPresentingSaveConfirm = false @State var initialLoad: Bool = true @@ -61,8 +62,7 @@ struct DeviceConfig: View { HStack { Button("Reset NodeDB", role: .destructive) { - - isPresentingFactoryResetConfirm = true + isPresentingNodeDBResetConfirm = true } .disabled(bleManager.connectedPeripheral == nil) .buttonStyle(.bordered) @@ -71,19 +71,20 @@ struct DeviceConfig: View { .padding() .confirmationDialog( "Are you sure?", - isPresented: $isPresentingFactoryResetConfirm, + isPresented: $isPresentingNodeDBResetConfirm, titleVisibility: .visible ) { - Button("Erase the NodeDB from node and app?", role: .destructive) { - + Button("Erase all device and app data?", role: .destructive) { if !bleManager.sendNodeDBReset(destNum: bleManager.connectedPeripheral.num) { - print("NodeDB Reset Failed") + } else { + // Disconnect from device as we are going to wipe the app database now + bleManager.disconnectPeripheral() + clearCoreDataDatabase(context: context) } } } Button("Factory Reset", role: .destructive) { - isPresentingFactoryResetConfirm = true } .disabled(bleManager.connectedPeripheral == nil) @@ -96,11 +97,15 @@ struct DeviceConfig: View { isPresented: $isPresentingFactoryResetConfirm, titleVisibility: .visible ) { - Button("Erase all device settings?", role: .destructive) { + Button("Erase all device and app settings and data?", role: .destructive) { if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num) { print("Factory Reset Failed") + } else { + // Disconnect from device + bleManager.disconnectPeripheral() + clearCoreDataDatabase(context: context) } } }