diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 5fe23104..772276dd 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -155,6 +155,7 @@ DDDE5A1429AFEAB900490C6C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DDDE5A1229AFEAB900490C6C /* Assets.xcassets */; }; DDDEE5E129DA3E1100A8E078 /* NodeInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDDEE5E029DA3E1100A8E078 /* NodeInfoView.swift */; }; DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; + DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; DDFEB3BB29900C1200EE7472 /* CurrentConditionsCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */; }; /* End PBXBuildFile section */ @@ -366,6 +367,8 @@ DDDEE5E229DBE43E00A8E078 /* MeshtasticDataModelV11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV11.xcdatamodel; sourceTree = ""; }; DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsEnums.swift; sourceTree = ""; }; DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV4.xcdatamodel; sourceTree = ""; }; + DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV17.xcdatamodel; sourceTree = ""; }; + DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreForward.swift; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; DDFEB3BA29900C1200EE7472 /* CurrentConditionsCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentConditionsCompact.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -521,13 +524,14 @@ isa = PBXGroup; children = ( DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */, + DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */, DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */, DD2160AE28C5552500C17253 /* MQTTConfig.swift */, DD41582928585C32009B0E59 /* RangeTestConfig.swift */, DDC94FCD29CF55310082EA6E /* RtttlConfig.swift */, DD6193782863875F00E59241 /* SerialConfig.swift */, + DDF6B2472A9AEBF500BA6931 /* StoreForward.swift */, DD415827285859C4009B0E59 /* TelemetryConfig.swift */, - DDC4C9FE2A8D982900CE201C /* DetectionSensorConfig.swift */, ); path = Module; sourceTree = ""; @@ -1128,6 +1132,7 @@ DDC4C9FF2A8D982900CE201C /* DetectionSensorConfig.swift in Sources */, DD5E5210298EE33B00D21B61 /* telemetry.pb.swift in Sources */, DD5E5205298EE33B00D21B61 /* mesh.pb.swift in Sources */, + DDF6B2482A9AEBF500BA6931 /* StoreForward.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, DD41582A28585C32009B0E59 /* RangeTestConfig.swift in Sources */, DD1925B728CDA5A400720036 /* CannedMessagesConfigEnums.swift in Sources */, @@ -1645,6 +1650,7 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */, DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */, DD14E72C2A80738F006E39BC /* MeshtasticDataModelV15.xcdatamodel */, DD0E9C222A30CE3A00580CBB /* MeshtasticDataModelV14.xcdatamodel */, @@ -1662,7 +1668,7 @@ DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DDC4CA012A8DAA3800CE201C /* MeshtasticDataModelV16.xcdatamodel */; + currentVersion = DDF6B2462A9AEB9E00BA6931 /* MeshtasticDataModelV17.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 268b64af..ade1b866 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1384,6 +1384,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } + public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setModuleConfig.detectionSensor = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() @@ -1402,7 +1428,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved External Notification Module Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved External Notification Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertExternalNotificationModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) @@ -1427,7 +1453,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved RTTTL Ringtone Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved RTTTL Ringtone Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertRtttlConfigPacket(ringtone: ringtone, nodeNum: toUser.num, context: context!) @@ -1456,7 +1482,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate meshPacket.decoded = dataMessage - let messageDescription = "Saved WiFi Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved MQTT Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertMqttModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) @@ -1481,7 +1507,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Range Test Module Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved Range Test Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertRangeTestModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) @@ -1509,7 +1535,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Serial Module Config for \(toUser.longName ?? "unknown".localized)" + let messageDescription = "🛟 Saved Serial Module Config for \(toUser.longName ?? "unknown".localized)" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { upsertSerialModuleConfigPacket(config: config, nodeNum: toUser.num, context: context!) return Int64(meshPacket.id) @@ -1517,6 +1543,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } + public func saveStoreForwardModuleConfig(config: ModuleConfig.StoreForwardConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.setModuleConfig.storeForward = config + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() @@ -1543,32 +1595,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate return 0 } - public func saveDetectionSensorModuleConfig(config: ModuleConfig.DetectionSensorConfig, fromUser: UserEntity, toUser: UserEntity, adminIndex: Int32) -> Int64 { - - var adminPacket = AdminMessage() - adminPacket.setModuleConfig.detectionSensor = config - - var meshPacket: MeshPacket = MeshPacket() - meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 7ccbfdf1..6b72864b 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV16.xcdatamodel + MeshtasticDataModelV17.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents new file mode 100644 index 00000000..2f6acb17 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV17.xcdatamodel/contents @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index 5270461f..169f004d 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -650,6 +650,67 @@ func upsertCannedMessagesModuleConfigPacket(config: Meshtastic.ModuleConfig.Cann } } +func upsertDetectionSensorModuleConfigPacket(config: Meshtastic.ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) { + + let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) + MeshLogger.log("📈 \(logString)") + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { + return + } + // Found a node, save Detection Sensor Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].detectionSensorConfig == nil { + + let newConfig = DetectionSensorConfigEntity(context: context) + newConfig.enabled = config.enabled + newConfig.sendBell = config.sendBell + newConfig.name = config.name + + newConfig.monitorPin = Int32(config.monitorPin) + newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh + newConfig.usePullup = config.usePullup + newConfig.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) + newConfig.stateBroadcastSecs = Int32(config.stateBroadcastSecs) + fetchedNode[0].detectionSensorConfig = newConfig + + } else { + fetchedNode[0].detectionSensorConfig?.enabled = config.enabled + fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell + fetchedNode[0].detectionSensorConfig?.name = config.name + fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin) + fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup + fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh + fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) + fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(config.stateBroadcastSecs) + } + + do { + try context.save() + print("💾 Updated Detection Sensor Module Config for node number: \(String(nodeNum))") + + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Updating Core Data DetectionSensorConfigEntity: \(nsError)") + } + + } else { + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Detection Sensor Module Config") + } + + } catch { + let nsError = error as NSError + print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)") + } +} + func upsertExternalNotificationModuleConfigPacket(config: Meshtastic.ModuleConfig.ExternalNotificationConfig, nodeNum: Int64, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.externalnotification.config %@".localized, String(nodeNum)) @@ -919,6 +980,56 @@ func upsertSerialModuleConfigPacket(config: Meshtastic.ModuleConfig.SerialConfig } } +func upsertStoreForwardModuleConfigPacket(config: Meshtastic.ModuleConfig.StoreForwardConfig, nodeNum: Int64, context: NSManagedObjectContext) { + + let logString = String.localizedStringWithFormat("mesh.log.storeforward.config %@".localized, String(nodeNum)) + MeshLogger.log("📬 \(logString)") + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) + + do { + + guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { + return + } + // Found a node, save Store & Forward Sensor Config + if !fetchedNode.isEmpty { + + if fetchedNode[0].storeForwardConfig == nil { + + let newConfig = StoreForwardConfigEntity(context: context) + newConfig.enabled = config.enabled + newConfig.heartbeat = config.heartbeat + newConfig.records = Int32(config.records) + newConfig.historyReturnMax = Int32(config.historyReturnMax) + newConfig.historyReturnWindow = Int32(config.historyReturnWindow) + fetchedNode[0].storeForwardConfig = newConfig + + } else { + fetchedNode[0].storeForwardConfig?.enabled = config.enabled + fetchedNode[0].storeForwardConfig?.heartbeat = config.heartbeat + fetchedNode[0].storeForwardConfig?.records = Int32(config.records) + fetchedNode[0].storeForwardConfig?.historyReturnMax = Int32(config.historyReturnMax) + fetchedNode[0].storeForwardConfig?.historyReturnWindow = Int32(config.historyReturnWindow) + } + do { + try context.save() + print("💾 Updated Store & Forward Module Config for node number: \(String(nodeNum))") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Updating Core Data StoreForwardConfigEntity: \(nsError)") + } + } else { + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Store & Forward Module Config") + } + } catch { + let nsError = error as NSError + print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)") + } +} + func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.TelemetryConfig, nodeNum: Int64, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat("mesh.log.telemetry.config %@".localized, String(nodeNum)) @@ -972,64 +1083,3 @@ func upsertTelemetryModuleConfigPacket(config: Meshtastic.ModuleConfig.Telemetry print("💥 Fetching node for core data TelemetryConfigEntity failed: \(nsError)") } } - -func upsertDetectionSensorModuleConfigPacket(config: Meshtastic.ModuleConfig.DetectionSensorConfig, nodeNum: Int64, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat("mesh.log.detectionsensor.config %@".localized, String(nodeNum)) - MeshLogger.log("📈 \(logString)") - - let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") - fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) - - do { - - guard let fetchedNode = try context.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] else { - return - } - // Found a node, save Detection Sensor Config - if !fetchedNode.isEmpty { - - if fetchedNode[0].detectionSensorConfig == nil { - - let newConfig = DetectionSensorConfigEntity(context: context) - newConfig.enabled = config.enabled - newConfig.sendBell = config.sendBell - newConfig.name = config.name - - newConfig.monitorPin = Int32(config.monitorPin) - newConfig.detectionTriggeredHigh = config.detectionTriggeredHigh - newConfig.usePullup = config.usePullup - newConfig.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) - newConfig.stateBroadcastSecs = Int32(config.stateBroadcastSecs) - fetchedNode[0].detectionSensorConfig = newConfig - - } else { - fetchedNode[0].detectionSensorConfig?.enabled = config.enabled - fetchedNode[0].detectionSensorConfig?.sendBell = config.sendBell - fetchedNode[0].detectionSensorConfig?.name = config.name - fetchedNode[0].detectionSensorConfig?.monitorPin = Int32(config.monitorPin) - fetchedNode[0].detectionSensorConfig?.usePullup = config.usePullup - fetchedNode[0].detectionSensorConfig?.detectionTriggeredHigh = config.detectionTriggeredHigh - fetchedNode[0].detectionSensorConfig?.minimumBroadcastSecs = Int32(config.minimumBroadcastSecs) - fetchedNode[0].detectionSensorConfig?.stateBroadcastSecs = Int32(config.stateBroadcastSecs) - } - - do { - try context.save() - print("💾 Updated Detection Sensor Module Config for node number: \(String(nodeNum))") - - } catch { - context.rollback() - let nsError = error as NSError - print("💥 Error Updating Core Data DetectionSensorConfigEntity: \(nsError)") - } - - } else { - print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Detection Sensor Module Config") - } - - } catch { - let nsError = error as NSError - print("💥 Fetching node for core data DetectionSensorConfigEntity failed: \(nsError)") - } -} diff --git a/Meshtastic/Views/ContentView.swift b/Meshtastic/Views/ContentView.swift index f630d195..32f783b3 100644 --- a/Meshtastic/Views/ContentView.swift +++ b/Meshtastic/Views/ContentView.swift @@ -33,6 +33,7 @@ struct ContentView: View { Settings() .tabItem { Label("settings", systemImage: "gear") + .font(.title) } .tag(Tab.settings) } diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index dc490a84..f7930c44 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -29,7 +29,7 @@ struct EnvironmentMetricsLog: View { .sorted { $0.time! < $1.time! } let locale = NSLocale.current as NSLocale let localeUnit = locale.object(forKey: NSLocale.Key(rawValue: "kCFLocaleTemperatureUnitKey")) - var format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius + let format: UnitTemperature = localeUnit as? String ?? "Celsius" == "Fahrenheit" ? .fahrenheit : .celsius NavigationStack { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 8e3ad3c5..1ffc9997 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -53,7 +53,6 @@ struct NodeDetail: View { var body: some View { - let hwModelString = node.user?.hwModel ?? "UNSET" let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) NavigationStack { GeometryReader { bounds in diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 95df3f75..011c461d 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -32,7 +32,7 @@ struct PositionLog: View { let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma").replacingOccurrences(of: ",", with: "") if UIDevice.current.userInterfaceIdiom == .pad && !useGrid || UIDevice.current.userInterfaceIdiom == .mac { // Add a table for mac and ipad - var positions = node.positions?.reversed() as? [PositionEntity] ?? [] + let positions = node.positions?.reversed() as? [PositionEntity] ?? [] Table(positions) { TableColumn("Latitude") { position in diff --git a/Meshtastic/Views/Settings/About.swift b/Meshtastic/Views/Settings/About.swift index 8edb32a4..b395a9c0 100644 --- a/Meshtastic/Views/Settings/About.swift +++ b/Meshtastic/Views/Settings/About.swift @@ -36,7 +36,7 @@ struct AboutMeshtastic: View { } if locale.region?.identifier ?? "no locale" == "US" { Section(header: Text("Get Devices")) { - Link("Buy Complete Radios", destination: URL(string: "https://www.etsy.com/shop/GarthVH")!) + Link("Buy Complete Radios", destination: URL(string: "http://garthvh.com")!) .font(.title2) } } diff --git a/Meshtastic/Views/Settings/Config/Module/StoreForward.swift b/Meshtastic/Views/Settings/Config/Module/StoreForward.swift new file mode 100644 index 00000000..e2572a14 --- /dev/null +++ b/Meshtastic/Views/Settings/Config/Module/StoreForward.swift @@ -0,0 +1,185 @@ +// +// StoreForward.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 8/26/23. +// + +import SwiftUI + +struct StoreForwardConfig: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.dismiss) private var goBack + var node: NodeInfoEntity? + @State private var isPresentingSaveConfirm: Bool = false + @State var hasChanges: Bool = false + /// Enable the Store and Forward Module + @State var enabled = false + /// Send a Heartbeat + @State var heartbeat: Bool = false + /// Number of Records + @State var records = 0 + /// Max number of history items to return + @State var historyReturnMax = 0 + /// Time window for history + @State var historyReturnWindow = 0 + + var body: some View { + + Form { + if node != nil && node?.metadata == nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { + Text("There has been no response to a request for device metadata over the admin channel for this node.") + .font(.callout) + .foregroundColor(.orange) + + } else if node != nil && node?.num ?? 0 != bleManager.connectedPeripheral?.num ?? 0 { + // Let users know what is going on if they are using remote admin and don't have the config yet + if node?.detectionSensorConfig == nil { + Text("Detection Sensor config data was requested over the admin channel but no response has been returned from the remote node. You can check the status of admin message requests in the admin message log.") + .font(.callout) + .foregroundColor(.orange) + } else { + Text("Remote administration for: \(node?.user?.longName ?? "Unknown")") + .font(.title3) + .onAppear { + setDetectionSensorValues() + } + } + } else if node != nil && node?.num ?? 0 == bleManager.connectedPeripheral?.num ?? 0 { + Text("Configuration for: \(node?.user?.longName ?? "Unknown")") + .font(.title3) + } else { + Text("Please connect to a radio to configure settings.") + .font(.callout) + .foregroundColor(.orange) + } + Section(header: Text("options")) { + Toggle(isOn: $enabled) { + Label("enabled", systemImage: "envelope.arrow.triangle.branch") + } + Toggle(isOn: $heartbeat) { + Label("storeforward.heartbeat", systemImage: "waveform.path.ecg") + } + Picker("Number of records", selection: $records) { + Text("unset").tag(0) + Text("25").tag(25) + Text("50").tag(50) + Text("75").tag(75) + Text("100").tag(100) + } + .pickerStyle(DefaultPickerStyle()) + Picker("History Return Max", selection: $historyReturnMax ) { + Text("unset").tag(0) + Text("25").tag(25) + Text("50").tag(50) + Text("75").tag(75) + Text("100").tag(100) + } + .pickerStyle(DefaultPickerStyle()) + Picker("History Return Window", selection: $historyReturnWindow ) { + Text("unset").tag(0) + Text("One Hour").tag(60) + Text("Two Hours").tag(120) + Text("Four Hours").tag(240) + Text("Six Hours").tag(360) + } + .pickerStyle(DefaultPickerStyle()) + } + } + .scrollDismissesKeyboard(.interactively) + .disabled(self.bleManager.connectedPeripheral == nil || node?.detectionSensorConfig == nil) + + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral?.num ?? -1, context: context) + if connectedNode != nil { + let nodeName = node?.user?.longName ?? "unknown".localized + let buttonText = String.localizedStringWithFormat("save.config %@".localized, nodeName) + Button(buttonText) { + var sfc = ModuleConfig.StoreForwardConfig() + sfc.enabled = self.enabled + sfc.heartbeat = self.heartbeat + sfc.records = UInt32(self.records) + sfc.historyReturnMax = UInt32(self.historyReturnMax) + sfc.historyReturnWindow = UInt32(self.historyReturnWindow) + + let adminMessageId = bleManager.saveStoreForwardModuleConfig(config: sfc, fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + if adminMessageId > 0 { + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + goBack() + } } + } + } + message: { + Text("config.save.confirm") + } + .navigationTitle("storeforward.config") + .navigationBarItems(trailing: + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + self.bleManager.context = context + setDetectionSensorValues() + + // Need to request a Detection Sensor Module Config from the remote node before allowing changes + if bleManager.connectedPeripheral != nil && node?.detectionSensorConfig == nil { + print("empty store and forward module config") + let connectedNode = getNodeInfo(id: bleManager.connectedPeripheral.num, context: context) + if node != nil && connectedNode != nil { + _ = bleManager.requestDetectionSensorModuleConfig(fromUser: connectedNode!.user!, toUser: node!.user!, adminIndex: connectedNode?.myInfo?.adminIndex ?? 0) + } + } + } + .onChange(of: enabled) { newEnabled in + if node != nil && node?.detectionSensorConfig != nil { + if newEnabled != node!.detectionSensorConfig!.enabled { hasChanges = true } + } + } + .onChange(of: heartbeat) { newHeartbeat in + if node != nil && node?.storeForwardConfig != nil { + if newHeartbeat != node!.storeForwardConfig!.heartbeat { hasChanges = true } + } + } + .onChange(of: records) { newRecords in + if node != nil && node?.storeForwardConfig != nil { + if newRecords != node!.storeForwardConfig!.records { hasChanges = true } + } + } + .onChange(of: historyReturnMax) { newHistoryReturnMax in + if node != nil && node?.storeForwardConfig != nil { + if newHistoryReturnMax != node!.storeForwardConfig!.historyReturnMax { hasChanges = true } + } + } + .onChange(of: historyReturnWindow) { newHistoryReturnWindow in + if node != nil && node?.storeForwardConfig != nil { + if newHistoryReturnWindow != node!.storeForwardConfig!.historyReturnWindow { hasChanges = true } + } + } + } + func setDetectionSensorValues() { + self.enabled = (node?.storeForwardConfig?.enabled ?? false) + self.heartbeat = (node?.storeForwardConfig?.heartbeat ?? true) + self.records = Int(node?.storeForwardConfig?.records ?? 50) + self.historyReturnMax = Int(node?.storeForwardConfig?.historyReturnMax ?? 100) + self.historyReturnWindow = Int(node?.storeForwardConfig?.historyReturnWindow ?? 60) + self.hasChanges = false + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 5b36fe6d..52e70d7c 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -258,6 +258,15 @@ struct Settings: View { } .tag(SettingsSidebar.serialConfig) + NavigationLink { + StoreForwardConfig(node: nodes.first(where: { $0.num == selectedNode })) + } label: { + Image(systemName: "envelope.arrow.triangle.branch") + .symbolRenderingMode(.hierarchical) + Text("storeforward") + } + .tag(SettingsSidebar.serialConfig) + NavigationLink { TelemetryConfig(node: nodes.first(where: { $0.num == selectedNode })) } label: { diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Contents.json b/Widgets/Assets.xcassets/m-logo-black.imageset/Contents.json similarity index 100% rename from Widgets/Assets.xcassets/logo-black.imageset/Contents.json rename to Widgets/Assets.xcassets/m-logo-black.imageset/Contents.json diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black.svg b/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black.svg rename to Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black.svg diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg b/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Large.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Large.svg rename to Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Large.svg diff --git a/Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg b/Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Small.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-black.imageset/Mesh_Logo_Black_Small.svg rename to Widgets/Assets.xcassets/m-logo-black.imageset/Mesh_Logo_Black_Small.svg diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Contents.json b/Widgets/Assets.xcassets/m-logo-white.imageset/Contents.json similarity index 100% rename from Widgets/Assets.xcassets/logo-white.imageset/Contents.json rename to Widgets/Assets.xcassets/m-logo-white.imageset/Contents.json diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White.svg b/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White.svg rename to Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White.svg diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg b/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Large.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Large.svg rename to Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Large.svg diff --git a/Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg b/Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Small.svg similarity index 100% rename from Widgets/Assets.xcassets/logo-white.imageset/Mesh_Logo_White_Small.svg rename to Widgets/Assets.xcassets/m-logo-white.imageset/Mesh_Logo_White_Small.svg diff --git a/Widgets/WidgetsLiveActivity.swift b/Widgets/WidgetsLiveActivity.swift index a9ffa68e..dd72d684 100644 --- a/Widgets/WidgetsLiveActivity.swift +++ b/Widgets/WidgetsLiveActivity.swift @@ -76,7 +76,7 @@ struct WidgetsLiveActivity: Widget { } } compactLeading: { - Image("logo-black") + Image("m-logo-black") .resizable() .frame(width: 30.0) .padding(4) @@ -87,7 +87,7 @@ struct WidgetsLiveActivity: Widget { .foregroundColor(Color("LightIndigo")) .frame(width: 40) } minimal: { - Image("logo-black") + Image("m-logo-black") .resizable() .frame(width: 24.0) .padding(4) @@ -137,7 +137,7 @@ struct LiveActivityView: View { var body: some View { HStack { - Image(colorScheme == .light ? "logo-black" : "logo-white") + Image(colorScheme == .light ? "m-logo-black" : "m-logo-white") .resizable() .clipShape(ContainerRelativeShape()) .opacity(isLuminanceReduced ? 0.5 : 1.0) diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 18db64fc..1786ac64 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -256,6 +256,9 @@ "set.region"="Set LoRa Region"; "standard"="Standard"; "standard.muted"="Standard Muted"; +"storeforward"="Store & Forward"; +"storeforward.config"="Store & Forward Config"; +"storeforward.heartbeat"="Send Heartbeat"; "ssid"="SSID"; "tapback"="Tapback Response"; "tapback.heart"="Heart"; diff --git a/swiftlint.geojson b/swiftlint.geojson new file mode 100644 index 00000000..5372466e --- /dev/null +++ b/swiftlint.geojson @@ -0,0 +1,6 @@ +{ + "type": "MultiPolygon", + "coordinates": [ + + ] +}