From 7f8821754f877fe91afd7e6e3ca674f3d6eb7a02 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 28 Dec 2022 21:15:39 -0800 Subject: [PATCH 01/40] Updated protos --- Meshtastic/Protobufs/mesh.pb.swift | 8 + Meshtastic/Protobufs/storeforward.pb.swift | 225 ++++++++++++++++----- Meshtastic/Protobufs/telemetry.pb.swift | 8 + 3 files changed, 185 insertions(+), 56 deletions(-) diff --git a/Meshtastic/Protobufs/mesh.pb.swift b/Meshtastic/Protobufs/mesh.pb.swift index a2dd9a94..f0671f23 100644 --- a/Meshtastic/Protobufs/mesh.pb.swift +++ b/Meshtastic/Protobufs/mesh.pb.swift @@ -94,6 +94,10 @@ enum HardwareModel: SwiftProtobuf.Enum { /// TODO: REPLACE case tloraV211P8 // = 15 + /// + /// TODO: REPLACE + case tloraT3S3 // = 16 + /// /// B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station case stationG1 // = 25 @@ -177,6 +181,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 13: self = .rak11200 case 14: self = .nanoG1 case 15: self = .tloraV211P8 + case 16: self = .tloraT3S3 case 25: self = .stationG1 case 32: self = .loraRelayV1 case 33: self = .nrf52840Dk @@ -214,6 +219,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .rak11200: return 13 case .nanoG1: return 14 case .tloraV211P8: return 15 + case .tloraT3S3: return 16 case .stationG1: return 25 case .loraRelayV1: return 32 case .nrf52840Dk: return 33 @@ -256,6 +262,7 @@ extension HardwareModel: CaseIterable { .rak11200, .nanoG1, .tloraV211P8, + .tloraT3S3, .stationG1, .loraRelayV1, .nrf52840Dk, @@ -2153,6 +2160,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 13: .same(proto: "RAK11200"), 14: .same(proto: "NANO_G1"), 15: .same(proto: "TLORA_V2_1_1P8"), + 16: .same(proto: "TLORA_T3_S3"), 25: .same(proto: "STATION_G1"), 32: .same(proto: "LORA_RELAY_V1"), 33: .same(proto: "NRF52840DK"), diff --git a/Meshtastic/Protobufs/storeforward.pb.swift b/Meshtastic/Protobufs/storeforward.pb.swift index b6df6719..2311daa4 100644 --- a/Meshtastic/Protobufs/storeforward.pb.swift +++ b/Meshtastic/Protobufs/storeforward.pb.swift @@ -31,44 +31,99 @@ struct StoreAndForward { /// TODO: REPLACE var rr: StoreAndForward.RequestResponse = .unset + /// + /// TODO: REPLACE + var variant: StoreAndForward.OneOf_Variant? = nil + /// /// TODO: REPLACE var stats: StoreAndForward.Statistics { - get {return _stats ?? StoreAndForward.Statistics()} - set {_stats = newValue} + get { + if case .stats(let v)? = variant {return v} + return StoreAndForward.Statistics() + } + set {variant = .stats(newValue)} } - /// Returns true if `stats` has been explicitly set. - var hasStats: Bool {return self._stats != nil} - /// Clears the value of `stats`. Subsequent reads from it will return its default value. - mutating func clearStats() {self._stats = nil} /// /// TODO: REPLACE var history: StoreAndForward.History { - get {return _history ?? StoreAndForward.History()} - set {_history = newValue} + get { + if case .history(let v)? = variant {return v} + return StoreAndForward.History() + } + set {variant = .history(newValue)} } - /// Returns true if `history` has been explicitly set. - var hasHistory: Bool {return self._history != nil} - /// Clears the value of `history`. Subsequent reads from it will return its default value. - mutating func clearHistory() {self._history = nil} /// /// TODO: REPLACE var heartbeat: StoreAndForward.Heartbeat { - get {return _heartbeat ?? StoreAndForward.Heartbeat()} - set {_heartbeat = newValue} + get { + if case .heartbeat(let v)? = variant {return v} + return StoreAndForward.Heartbeat() + } + set {variant = .heartbeat(newValue)} + } + + /// + /// Empty Payload + var empty: Bool { + get { + if case .empty(let v)? = variant {return v} + return false + } + set {variant = .empty(newValue)} } - /// Returns true if `heartbeat` has been explicitly set. - var hasHeartbeat: Bool {return self._heartbeat != nil} - /// Clears the value of `heartbeat`. Subsequent reads from it will return its default value. - mutating func clearHeartbeat() {self._heartbeat = nil} var unknownFields = SwiftProtobuf.UnknownStorage() /// - /// 1 - 99 = From Router - /// 101 - 199 = From Client + /// TODO: REPLACE + enum OneOf_Variant: Equatable { + /// + /// TODO: REPLACE + case stats(StoreAndForward.Statistics) + /// + /// TODO: REPLACE + case history(StoreAndForward.History) + /// + /// TODO: REPLACE + case heartbeat(StoreAndForward.Heartbeat) + /// + /// Empty Payload + case empty(Bool) + + #if !swift(>=4.1) + static func ==(lhs: StoreAndForward.OneOf_Variant, rhs: StoreAndForward.OneOf_Variant) -> Bool { + // 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 (lhs, rhs) { + case (.stats, .stats): return { + guard case .stats(let l) = lhs, case .stats(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.history, .history): return { + guard case .history(let l) = lhs, case .history(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.heartbeat, .heartbeat): return { + guard case .heartbeat(let l) = lhs, case .heartbeat(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.empty, .empty): return { + guard case .empty(let l) = lhs, case .empty(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif + } + + /// + /// 001 - 063 = From Router + /// 064 - 127 = From Client enum RequestResponse: SwiftProtobuf.Enum { typealias RawValue = Int @@ -101,26 +156,30 @@ struct StoreAndForward { /// Router is responding to a request for history. case routerHistory // = 6 + /// + /// Router is responding to a request for stats. + case routerStats // = 7 + /// /// Client is an in error state. - case clientError // = 101 + case clientError // = 64 /// /// Client has requested a replay from the router. - case clientHistory // = 102 + case clientHistory // = 65 /// /// Client has requested stats from the router. - case clientStats // = 103 + case clientStats // = 66 /// /// Client has requested the router respond. This can work as a /// "are you there" message. - case clientPing // = 104 + case clientPing // = 67 /// /// The response to a "Ping" - case clientPong // = 105 + case clientPong // = 68 /// /// Client has requested that the router abort processing the client's request @@ -140,11 +199,12 @@ struct StoreAndForward { case 4: self = .routerPong case 5: self = .routerBusy case 6: self = .routerHistory - case 101: self = .clientError - case 102: self = .clientHistory - case 103: self = .clientStats - case 104: self = .clientPing - case 105: self = .clientPong + case 7: self = .routerStats + case 64: self = .clientError + case 65: self = .clientHistory + case 66: self = .clientStats + case 67: self = .clientPing + case 68: self = .clientPong case 106: self = .clientAbort default: self = .UNRECOGNIZED(rawValue) } @@ -159,11 +219,12 @@ struct StoreAndForward { case .routerPong: return 4 case .routerBusy: return 5 case .routerHistory: return 6 - case .clientError: return 101 - case .clientHistory: return 102 - case .clientStats: return 103 - case .clientPing: return 104 - case .clientPong: return 105 + case .routerStats: return 7 + case .clientError: return 64 + case .clientHistory: return 65 + case .clientStats: return 66 + case .clientPing: return 67 + case .clientPong: return 68 case .clientAbort: return 106 case .UNRECOGNIZED(let i): return i } @@ -264,10 +325,6 @@ struct StoreAndForward { } init() {} - - fileprivate var _stats: StoreAndForward.Statistics? = nil - fileprivate var _history: StoreAndForward.History? = nil - fileprivate var _heartbeat: StoreAndForward.Heartbeat? = nil } #if swift(>=4.2) @@ -282,6 +339,7 @@ extension StoreAndForward.RequestResponse: CaseIterable { .routerPong, .routerBusy, .routerHistory, + .routerStats, .clientError, .clientHistory, .clientStats, @@ -295,6 +353,7 @@ extension StoreAndForward.RequestResponse: CaseIterable { #if swift(>=5.5) && canImport(_Concurrency) extension StoreAndForward: @unchecked Sendable {} +extension StoreAndForward.OneOf_Variant: @unchecked Sendable {} extension StoreAndForward.RequestResponse: @unchecked Sendable {} extension StoreAndForward.Statistics: @unchecked Sendable {} extension StoreAndForward.History: @unchecked Sendable {} @@ -310,6 +369,7 @@ extension StoreAndForward: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen 2: .same(proto: "stats"), 3: .same(proto: "history"), 4: .same(proto: "heartbeat"), + 5: .same(proto: "empty"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -319,9 +379,53 @@ extension StoreAndForward: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self.rr) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._stats) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._history) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._heartbeat) }() + case 2: try { + var v: StoreAndForward.Statistics? + var hadOneofValue = false + if let current = self.variant { + hadOneofValue = true + if case .stats(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.variant = .stats(v) + } + }() + case 3: try { + var v: StoreAndForward.History? + var hadOneofValue = false + if let current = self.variant { + hadOneofValue = true + if case .history(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.variant = .history(v) + } + }() + case 4: try { + var v: StoreAndForward.Heartbeat? + var hadOneofValue = false + if let current = self.variant { + hadOneofValue = true + if case .heartbeat(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.variant = .heartbeat(v) + } + }() + case 5: try { + var v: Bool? + try decoder.decodeSingularBoolField(value: &v) + if let v = v { + if self.variant != nil {try decoder.handleConflictingOneOf()} + self.variant = .empty(v) + } + }() default: break } } @@ -335,23 +439,31 @@ extension StoreAndForward: SwiftProtobuf.Message, SwiftProtobuf._MessageImplemen if self.rr != .unset { try visitor.visitSingularEnumField(value: self.rr, fieldNumber: 1) } - try { if let v = self._stats { + switch self.variant { + case .stats?: try { + guard case .stats(let v)? = self.variant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._history { + }() + case .history?: try { + guard case .history(let v)? = self.variant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._heartbeat { + }() + case .heartbeat?: try { + guard case .heartbeat(let v)? = self.variant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() + }() + case .empty?: try { + guard case .empty(let v)? = self.variant else { preconditionFailure() } + try visitor.visitSingularBoolField(value: v, fieldNumber: 5) + }() + case nil: break + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: StoreAndForward, rhs: StoreAndForward) -> Bool { if lhs.rr != rhs.rr {return false} - if lhs._stats != rhs._stats {return false} - if lhs._history != rhs._history {return false} - if lhs._heartbeat != rhs._heartbeat {return false} + if lhs.variant != rhs.variant {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -366,11 +478,12 @@ extension StoreAndForward.RequestResponse: SwiftProtobuf._ProtoNameProviding { 4: .same(proto: "ROUTER_PONG"), 5: .same(proto: "ROUTER_BUSY"), 6: .same(proto: "ROUTER_HISTORY"), - 101: .same(proto: "CLIENT_ERROR"), - 102: .same(proto: "CLIENT_HISTORY"), - 103: .same(proto: "CLIENT_STATS"), - 104: .same(proto: "CLIENT_PING"), - 105: .same(proto: "CLIENT_PONG"), + 7: .same(proto: "ROUTER_STATS"), + 64: .same(proto: "CLIENT_ERROR"), + 65: .same(proto: "CLIENT_HISTORY"), + 66: .same(proto: "CLIENT_STATS"), + 67: .same(proto: "CLIENT_PING"), + 68: .same(proto: "CLIENT_PONG"), 106: .same(proto: "CLIENT_ABORT"), ] } diff --git a/Meshtastic/Protobufs/telemetry.pb.swift b/Meshtastic/Protobufs/telemetry.pb.swift index f0404197..de098cdc 100644 --- a/Meshtastic/Protobufs/telemetry.pb.swift +++ b/Meshtastic/Protobufs/telemetry.pb.swift @@ -72,6 +72,10 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { /// /// 3-Axis magnetic sensor case qmc5883L // = 11 + + /// + /// High accuracy temperature and humidity + case sht31 // = 12 case UNRECOGNIZED(Int) init() { @@ -92,6 +96,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { case 9: self = .qmc6310 case 10: self = .qmi8658 case 11: self = .qmc5883L + case 12: self = .sht31 default: self = .UNRECOGNIZED(rawValue) } } @@ -110,6 +115,7 @@ enum TelemetrySensorType: SwiftProtobuf.Enum { case .qmc6310: return 9 case .qmi8658: return 10 case .qmc5883L: return 11 + case .sht31: return 12 case .UNRECOGNIZED(let i): return i } } @@ -133,6 +139,7 @@ extension TelemetrySensorType: CaseIterable { .qmc6310, .qmi8658, .qmc5883L, + .sht31, ] } @@ -296,6 +303,7 @@ extension TelemetrySensorType: SwiftProtobuf._ProtoNameProviding { 9: .same(proto: "QMC6310"), 10: .same(proto: "QMI8658"), 11: .same(proto: "QMC5883L"), + 12: .same(proto: "SHT31"), ] } From 463e8230eef6432db68fb721b4bcd4956f93ea1e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 29 Dec 2022 16:26:00 -0800 Subject: [PATCH 02/40] * Real Ack UI and data structure for DM's * Save canned messages messages to core data --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Helpers/MeshPackets.swift | 39 ++- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 279 ++++++++++++++++++ .../Views/Messages/UserMessageList.swift | 3 +- .../Config/Module/CannedMessagesConfig.swift | 6 + de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 1 + 8 files changed, 329 insertions(+), 6 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV5.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 50e4621a..ab1f5b8b 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -152,6 +152,7 @@ DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = ""; }; DD41582928585C32009B0E59 /* RangeTestConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RangeTestConfig.swift; sourceTree = ""; }; DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalStrengthIndicator.swift; sourceTree = ""; }; + DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV5.xcdatamodel; sourceTree = ""; }; DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = ""; }; DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = ""; }; DD47E3D826F3093800029299 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = ""; }; @@ -1210,12 +1211,13 @@ DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */, DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */, DDCDC69A29467643004C1DDA /* MeshtasticDataModelV3.xcdatamodel */, DD5D0A9A2931AD6B00F7EA61 /* MeshtasticDataModelV2.xcdatamodel */, DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */, ); - currentVersion = DDEE03EC29544A1000FCAD57 /* MeshtasticDataModelV4.xcdatamodel */; + currentVersion = DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f6336b9a..0606f543 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1076,9 +1076,35 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - if let messages = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { - print(messages) + if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { + + let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + let fetchedNode = try context.fetch(fetchNodeRequest) as! [NodeInfoEntity] + if fetchedNode.count == 1 { + + let messages = String(cmmc.textFormatString()) + .replacingOccurrences(of: "11: ", with: "") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + fetchedNode[0].cannedMessageConfig?.messages = messages + do { + try context.save() + MeshLogger.log("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") + } catch { + + context.rollback() + + let nsError = error as NSError + print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") + } + } + + } catch { + print("💥 Error Deserializing POSITION_APP packet.") + } } } @@ -1184,6 +1210,13 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana do { let fetchedMessage = try context.fetch(fetchMessageRequest) as? [MessageEntity] if fetchedMessage?.count ?? 0 > 0 { + + if fetchedMessage![0].toUser != nil { + // Real ACK from DM Recipient + if packet.to != packet.from { + fetchedMessage![0].realACK = true + } + } fetchedMessage![0].ackError = Int32(routingMessage.errorReason.rawValue) if routingMessage.errorReason == Routing.Error.none { diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index a8bccd83..1b3c637e 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModelV4.xcdatamodel + MeshtasticDataModelV5.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV5.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV5.xcdatamodel/contents new file mode 100644 index 00000000..1f463d49 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV5.xcdatamodel/contents @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 83fd25f3..a9452616 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -116,6 +116,7 @@ struct UserMessageList: View { if currentUser && message.receivedACK { VStack { Text("received.ack")+Text(" \(message.receivedACK ? "✔️" : "")") + Text("received.ack.real")+Text(" \(message.realACK ? "✔️" : "")") } } else if currentUser && message.ackError == 0 { // Empty Error @@ -181,7 +182,7 @@ struct UserMessageList: View { let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) if currentUser && message.receivedACK { // Ack Received - Text("\(ackErrorVal?.display ?? "No Error" )").font(.caption2).foregroundColor(.gray) + Text("\(ackErrorVal?.display ?? "No Error" )").font(.caption2).foregroundColor(message.realACK ? .gray : .orange) } else if currentUser && message.ackError == 0 { // Empty Error Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange) diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index a7b16b39..65922a38 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -261,6 +261,10 @@ struct CannedMessagesConfig: View { // Should show a saved successfully alert once I know that to be true // for now just disable the button after a successful save hasMessagesChanges = false + if !hasChanges { + bleManager.sendReboot(destNum: node!.num) + goBack() + } } } } @@ -282,7 +286,9 @@ struct CannedMessagesConfig: View { self.inputbrokerEventCw = Int(node?.cannedMessageConfig?.inputbrokerEventCw ?? 0) self.inputbrokerEventCcw = Int(node?.cannedMessageConfig?.inputbrokerEventCcw ?? 0) self.inputbrokerEventPress = Int(node?.cannedMessageConfig?.inputbrokerEventPress ?? 0) + self.messages = node?.cannedMessageConfig?.messages ?? "" self.hasChanges = false + self.hasMessagesChanges = false } .onChange(of: configPreset) { newPreset in diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index bde184d0..bc42706c 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -133,6 +133,7 @@ "range.test.config"="Entfernungstest Konfiguration"; "reply"="Antworten"; "received.ack"="Empfangsbestätigung"; +"received.ack.real"="Recipient Ack"; "routing.acknowledged"="Bestätigt"; "routing.noroute"="Keine Route"; "routing.gotnak"="Negative Empfangsbestätigung empfangen"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index dbe62ed9..0f9c881e 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -133,6 +133,7 @@ "range.test.config"="Range Test Config"; "reply"="Reply"; "received.ack"="Received Ack"; +"received.ack.real"="Recipient Ack"; "routing.acknowledged"="Acknowledged"; "routing.noroute"="No Route"; "routing.gotnak"="Received a negative acknowledgment"; From d4e5d44520b960d7ce52ce570e52bc9169432a56 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 29 Dec 2022 16:27:06 -0800 Subject: [PATCH 03/40] Uncomment channel editor --- Meshtastic/Views/Settings/Channels.swift | 570 +++++++++++------------ Meshtastic/Views/Settings/Settings.swift | 20 +- 2 files changed, 295 insertions(+), 295 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 486c3693..427b1dd3 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -1,288 +1,288 @@ -//// -//// ShareChannel.swift -//// MeshtasticApple -//// -//// Copyright(c) Garth Vander Houwen 4/8/22. -//// -//import SwiftUI -//import CoreData // -//func generateChannelKey(size: Int) -> String { -// var keyData = Data(count: size) -// _ = keyData.withUnsafeMutableBytes { -// SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!) -// } -// return keyData.base64EncodedString() -//} +// ShareChannel.swift +// MeshtasticApple // -//struct Channels: View { -// -// @Environment(\.managedObjectContext) var context -// @EnvironmentObject var bleManager: BLEManager -// @Environment(\.dismiss) private var goBack -// @Environment(\.sizeCategory) var sizeCategory +// Copyright(c) Garth Vander Houwen 4/8/22. // -// -// var node: NodeInfoEntity? -// -// @State var hasChanges = false -// @State private var isPresentingEditView = false -// @State private var isPresentingSaveConfirm: Bool = false -// @State private var channelIndex: Int32 = 0 -// @State private var channelName = "" -// @State private var channelKeySize = 32 -// @State private var channelKey = "AQ==" -// @State private var channelRole = 0 -// @State private var uplink = false -// @State private var downlink = false -// -// var body: some View { -// -// NavigationStack { -// List { -// if node != nil && node?.myInfo != nil { -// ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in -// Button(action: { -// channelIndex = channel.index -// channelRole = Int(channel.role) -// channelKey = channel.psk?.base64EncodedString() ?? "" -// if channelKey.count == 0 { -// channelKeySize = 0 -// } else if channelKey == "AQ==" { -// channelKeySize = -1 -// } else if channelKey.count == 24 { -// channelKeySize = 16 -// } else if channelKey.count == 32 { -// channelKeySize = 24 -// } else if channelKey.count == 44 { -// channelKeySize = 32 -// } -// channelName = channel.name ?? "" -// uplink = channel.uplinkEnabled -// downlink = channel.downlinkEnabled -// isPresentingEditView = true -// hasChanges = false -// }) { -// VStack(alignment: .leading) { -// HStack { -// CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1) -// .padding(.trailing, 5) -// VStack { -// HStack { -// if channel.name?.isEmpty ?? false { -// if channel.role == 1 { -// Text(String("PrimaryChannel").camelCaseToWords()).font(.headline) -// } else { -// Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline) -// } -// } else { -// Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline) -// } -// } -// } -// } -// } -// } -// } -// } -// } -// if node?.myInfo?.channels?.array.count ?? 0 < 8 { -// -// Button { -// let key = generateChannelKey(size: 32) -// channelName = "" -// channelIndex = Int32(node!.myInfo!.channels!.array.count) -// channelRole = 2 -// channelKey = key -// uplink = false -// downlink = false -// hasChanges = false -// isPresentingEditView = true -// -// } label: { -// Label("Add Channel", systemImage: "plus.square") -// } -// .buttonStyle(.bordered) -// .buttonBorderShape(.capsule) -// .controlSize(.large) -// .padding() -// .sheet(isPresented: $isPresentingEditView) { -// -// #if targetEnvironment(macCatalyst) -// Text("channel") -// .font(.largeTitle) -// .padding() -// #endif -// Form { -// HStack { -// Text("name") -// Spacer() -// TextField( -// "Channel Name", -// text: $channelName -// ) -// .disableAutocorrection(true) -// .keyboardType(.alphabet) -// .foregroundColor(Color.gray) -// .disabled(channelRole == 1 && channelName.count > 0) -// .onChange(of: channelName, perform: { value in -// channelName = channelName.replacing(" ", with: "") -// let totalBytes = channelName.utf8.count -// // Only mess with the value if it is too big -// if totalBytes > 11 { -// let firstNBytes = Data(channelName.utf8.prefix(11)) -// if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { -// // Set the channelName back to the last place where it was the right size -// channelName = maxBytesString -// } -// } -// hasChanges = true -// }) -// } -// HStack { -// Picker("Key Size", selection: $channelKeySize) { -// Text("Empty").tag(0) -// Text("Default").tag(-1) -// Text("1 bit").tag(1) -// Text("128 bit").tag(16) -// Text("192 bit").tag(24) -// Text("256 bit").tag(32) -// } -// .pickerStyle(DefaultPickerStyle()) -// Spacer() -// Button { -// if channelKeySize == -1 { -// channelKey = "AQ==" -// } else { -// let key = generateChannelKey(size: channelKeySize) -// channelKey = key -// } -// } label: { -// Image(systemName: "lock.rotation") -// .font(.title) -// } -// .buttonStyle(.bordered) -// .buttonBorderShape(.capsule) -// .controlSize(.small) -// } -// HStack (alignment: .top) { -// Text("Key") -// Spacer() -// TextField ( -// "", -// text: $channelKey, -// axis: .vertical -// ) -// .foregroundColor(Color.gray) -// .disabled(true) -// -// } -// .textSelection(.enabled) -// Picker("Channel Role", selection: $channelRole) { -// if channelRole == 1 { -// Text("Primary").tag(1) -// } else{ -// Text("Disabled").tag(0) -// Text("Secondary").tag(2) -// } -// } -// .pickerStyle(DefaultPickerStyle()) -// .disabled(channelRole == 1) -// Toggle("Uplink Enabled", isOn: $uplink) -// .toggleStyle(SwitchToggleStyle(tint: .accentColor)) -// Toggle("Downlink Enabled", isOn: $downlink) -// .toggleStyle(SwitchToggleStyle(tint: .accentColor)) -// } -// .onSubmit { -// //validate(name: channelName) -// } -// .onChange(of: channelName) { newName in -// hasChanges = true -// } -// .onChange(of: channelKeySize) { newKeySize in -// if channelKeySize == -1 { -// channelKey = "AQ==" -// } else { -// let key = generateChannelKey(size: channelKeySize) -// channelKey = key -// } -// hasChanges = true -// } -// .onChange(of: channelKey) { newKey in -// hasChanges = true -// } -// .onChange(of: channelRole) { newRole in -// hasChanges = true -// } -// .onChange(of: uplink) { newUplink in -// hasChanges = true -// } -// .onChange(of: downlink) { newDownlink in -// hasChanges = true -// } -// HStack { -// Button { -// isPresentingSaveConfirm = true -// } label: { -// Label("save", systemImage: "square.and.arrow.down") -// } -// .disabled(bleManager.connectedPeripheral == nil || !hasChanges) -// .buttonStyle(.bordered) -// .buttonBorderShape(.capsule) -// .controlSize(.large) -// .padding(.bottom) -// .confirmationDialog( -// "are.you.sure", -// isPresented: $isPresentingSaveConfirm, -// titleVisibility: .visible -// ) { -// Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { -// -// var channel = Channel() -// channel.index = channelIndex -// channel.settings.id = UInt32(channelIndex) -// channel.settings.name = channelName -// channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() -// channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary -// channel.settings.uplinkEnabled = uplink -// channel.settings.downlinkEnabled = downlink -// -// let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) -// -// if adminMessageId > 0 { -// // Should show a saved successfully alert once I know that to be true -// // for now just disable the button after a successful save -// channelName = "" -// hasChanges = false -// isPresentingEditView = false -// bleManager.disconnectPeripheral() -// } -// } -// } -// #if targetEnvironment(macCatalyst) -// Button { -// isPresentingEditView = false -// } label: { -// Label("Close", systemImage: "xmark") -// } -// .buttonStyle(.bordered) -// .buttonBorderShape(.capsule) -// .controlSize(.large) -// .padding(.bottom) -// #endif -// } -// .presentationDetents([.medium, .large]) -// } -// } -// } -// .navigationTitle("channels") -// .navigationSplitViewStyle(.automatic) -// .navigationBarItems(trailing: -// ZStack { -// ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") -// }) -// .onAppear { -// bleManager.context = context -// } -// } -//} +import SwiftUI +import CoreData + +func generateChannelKey(size: Int) -> String { + var keyData = Data(count: size) + _ = keyData.withUnsafeMutableBytes { + SecRandomCopyBytes(kSecRandomDefault, size, $0.baseAddress!) + } + return keyData.base64EncodedString() +} + +struct Channels: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + @Environment(\.dismiss) private var goBack + @Environment(\.sizeCategory) var sizeCategory + + + var node: NodeInfoEntity? + + @State var hasChanges = false + @State private var isPresentingEditView = false + @State private var isPresentingSaveConfirm: Bool = false + @State private var channelIndex: Int32 = 0 + @State private var channelName = "" + @State private var channelKeySize = 32 + @State private var channelKey = "AQ==" + @State private var channelRole = 0 + @State private var uplink = false + @State private var downlink = false + + var body: some View { + + NavigationStack { + List { + if node != nil && node?.myInfo != nil { + ForEach(node!.myInfo!.channels?.array as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in + Button(action: { + channelIndex = channel.index + channelRole = Int(channel.role) + channelKey = channel.psk?.base64EncodedString() ?? "" + if channelKey.count == 0 { + channelKeySize = 0 + } else if channelKey == "AQ==" { + channelKeySize = -1 + } else if channelKey.count == 24 { + channelKeySize = 16 + } else if channelKey.count == 32 { + channelKeySize = 24 + } else if channelKey.count == 44 { + channelKeySize = 32 + } + channelName = channel.name ?? "" + uplink = channel.uplinkEnabled + downlink = channel.downlinkEnabled + isPresentingEditView = true + hasChanges = false + }) { + VStack(alignment: .leading) { + HStack { + CircleText(text: String(channel.index), color: .accentColor, circleSize: 45, fontSize: 36, brightness: 0.1) + .padding(.trailing, 5) + VStack { + HStack { + if channel.name?.isEmpty ?? false { + if channel.role == 1 { + Text(String("PrimaryChannel").camelCaseToWords()).font(.headline) + } else { + Text(String("Channel \(channel.index)").camelCaseToWords()).font(.headline) + } + } else { + Text(String(channel.name ?? "Channel \(channel.index)").camelCaseToWords()).font(.headline) + } + } + } + } + } + } + } + } + } + if node?.myInfo?.channels?.array.count ?? 0 < 8 { + + Button { + let key = generateChannelKey(size: 32) + channelName = "" + channelIndex = Int32(node!.myInfo!.channels!.array.count) + channelRole = 2 + channelKey = key + uplink = false + downlink = false + hasChanges = false + isPresentingEditView = true + + } label: { + Label("Add Channel", systemImage: "plus.square") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .sheet(isPresented: $isPresentingEditView) { + + #if targetEnvironment(macCatalyst) + Text("channel") + .font(.largeTitle) + .padding() + #endif + Form { + HStack { + Text("name") + Spacer() + TextField( + "Channel Name", + text: $channelName + ) + .disableAutocorrection(true) + .keyboardType(.alphabet) + .foregroundColor(Color.gray) + .disabled(channelRole == 1 && channelName.count > 0) + .onChange(of: channelName, perform: { value in + channelName = channelName.replacing(" ", with: "") + let totalBytes = channelName.utf8.count + // Only mess with the value if it is too big + if totalBytes > 11 { + let firstNBytes = Data(channelName.utf8.prefix(11)) + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + // Set the channelName back to the last place where it was the right size + channelName = maxBytesString + } + } + hasChanges = true + }) + } + HStack { + Picker("Key Size", selection: $channelKeySize) { + Text("Empty").tag(0) + Text("Default").tag(-1) + Text("1 bit").tag(1) + Text("128 bit").tag(16) + Text("192 bit").tag(24) + Text("256 bit").tag(32) + } + .pickerStyle(DefaultPickerStyle()) + Spacer() + Button { + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key + } + } label: { + Image(systemName: "lock.rotation") + .font(.title) + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.small) + } + HStack (alignment: .top) { + Text("Key") + Spacer() + TextField ( + "", + text: $channelKey, + axis: .vertical + ) + .foregroundColor(Color.gray) + .disabled(true) + + } + .textSelection(.enabled) + Picker("Channel Role", selection: $channelRole) { + if channelRole == 1 { + Text("Primary").tag(1) + } else{ + Text("Disabled").tag(0) + Text("Secondary").tag(2) + } + } + .pickerStyle(DefaultPickerStyle()) + .disabled(channelRole == 1) + Toggle("Uplink Enabled", isOn: $uplink) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Toggle("Downlink Enabled", isOn: $downlink) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + .onSubmit { + //validate(name: channelName) + } + .onChange(of: channelName) { newName in + hasChanges = true + } + .onChange(of: channelKeySize) { newKeySize in + if channelKeySize == -1 { + channelKey = "AQ==" + } else { + let key = generateChannelKey(size: channelKeySize) + channelKey = key + } + hasChanges = true + } + .onChange(of: channelKey) { newKey in + hasChanges = true + } + .onChange(of: channelRole) { newRole in + hasChanges = true + } + .onChange(of: uplink) { newUplink in + hasChanges = true + } + .onChange(of: downlink) { newDownlink in + hasChanges = true + } + HStack { + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var channel = Channel() + channel.index = channelIndex + channel.settings.id = UInt32(channelIndex) + channel.settings.name = channelName + channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() + channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary + channel.settings.uplinkEnabled = uplink + channel.settings.downlinkEnabled = downlink + + let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) + + if adminMessageId > 0 { + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + channelName = "" + hasChanges = false + isPresentingEditView = false + bleManager.disconnectPeripheral() + } + } + } + #if targetEnvironment(macCatalyst) + Button { + isPresentingEditView = false + } label: { + Label("Close", systemImage: "xmark") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding(.bottom) + #endif + } + .presentationDetents([.medium, .large]) + } + } + } + .navigationTitle("channels") + .navigationSplitViewStyle(.automatic) + .navigationBarItems(trailing: + ZStack { + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + bleManager.context = context + } + } +} diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 9bb805f7..1ec48400 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -59,16 +59,16 @@ struct Settings: View { Text("lora") } -// NavigationLink() { -// -// Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) -// } label: { -// -// Image(systemName: "fibrechannel") -// .symbolRenderingMode(.hierarchical) -// -// Text("channels") -// } + NavigationLink() { + + Channels(node: nodes.first(where: { $0.num == connectedNodeNum })) + } label: { + + Image(systemName: "fibrechannel") + .symbolRenderingMode(.hierarchical) + + Text("channels") + } NavigationLink() { From 9f23ea3feac2bc00d9598ed2709c88ca5b4ee3a9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 29 Dec 2022 16:36:43 -0800 Subject: [PATCH 04/40] Additionally localized strings --- Meshtastic/Views/Bluetooth/InvalidVersion.swift | 2 +- Meshtastic/Views/Settings/Channels.swift | 2 +- Meshtastic/Views/Settings/ShareChannels.swift | 10 +++++----- de.lproj/Localizable.strings | 4 ++++ en.lproj/Localizable.strings | 4 ++++ 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Meshtastic/Views/Bluetooth/InvalidVersion.swift b/Meshtastic/Views/Bluetooth/InvalidVersion.swift index d4539b54..989723aa 100644 --- a/Meshtastic/Views/Bluetooth/InvalidVersion.swift +++ b/Meshtastic/Views/Bluetooth/InvalidVersion.swift @@ -51,7 +51,7 @@ struct InvalidVersion: View { Button { dismiss() } label: { - Label("Close", systemImage: "xmark") + Label("close", systemImage: "xmark") } .buttonStyle(.bordered) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 427b1dd3..2f70dfcd 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -263,7 +263,7 @@ struct Channels: View { Button { isPresentingEditView = false } label: { - Label("Close", systemImage: "xmark") + Label("close", systemImage: "xmark") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 5810b5cf..663926a3 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -56,15 +56,15 @@ struct ShareChannels: View { Grid() { GridRow { Spacer() - Text("Include") + Text("include") .font(.caption) .fontWeight(.bold) .padding(.trailing) - Text("Channel") + Text("channel") .font(.caption) .fontWeight(.bold) .padding(.trailing) - Text("Encrypted") + Text("encrypted") .font(.caption) .fontWeight(.bold) } @@ -246,7 +246,7 @@ struct ShareChannels: View { Button { isPresentingHelp = false } label: { - Label("Close", systemImage: "xmark") + Label("close", systemImage: "xmark") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) @@ -254,7 +254,7 @@ struct ShareChannels: View { .padding() #endif } - .navigationTitle("Generate QR Code") + .navigationTitle("generate.qr.code") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: ZStack { diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index bc42706c..17586228 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -35,6 +35,7 @@ "channel.role.secondary"="Sekundär"; "channels"="Kanäle"; "clear.app.data"="App Daten löschen"; +"close"="Close"; "connected.radio"="Verbundenes Gerät"; "communicating"="Verbinde mit Gerät..."; "connected"="Derzeit verbunden"; @@ -58,9 +59,11 @@ "echo"="Echo"; "email.address"="Email Adresse"; "enabled"="Aktiviert"; +"encrypted"="Encrypted"; "external.notification"="Externe Benachrichtigung"; "external.notification.config"="Einstellungen der externen Benachrichtigung"; "firmware.version"="Firmware Version"; +"generate.qr.code"="Generate QR Code"; "gpsformat.dec"="Dezimalgrad Format"; "gpsformat.dms"="Grad Minuten Sekunden"; "gpsformat.utm"="Universal Transversal Mercator"; @@ -70,6 +73,7 @@ "heard"="Gehört"; "heard.last"="Zuletzt gehört"; "hybrid"="Hybrid"; +"include"="Include"; "inputevent.none"="Keins"; "inputevent.up"="Hoch"; "inputevent.down"="Runter"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 0f9c881e..8ce5133e 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -35,6 +35,7 @@ "channel.role.secondary"="Secondary"; "channels"="Channels"; "clear.app.data"="Clear App Data"; +"close"="Close"; "connected.radio"="Connected Radio"; "communicating"="Communicating with device. ."; "connected"="Currently Connected"; @@ -58,9 +59,11 @@ "echo"="Echo"; "email.address"="Email Address"; "enabled"="Enabled"; +"encrypted"="Encrypted"; "external.notification"="External Notification"; "external.notification.config"="External Notification Config"; "firmware.version"="Firmware Version"; +"generate.qr.code"="Generate QR Code"; "gpsformat.dec"="Decimal Degrees Format"; "gpsformat.dms"="Degrees Minutes Seconds"; "gpsformat.utm"="Universal Transverse Mercator"; @@ -70,6 +73,7 @@ "heard"="Heard"; "heard.last"="Last Heard"; "hybrid"="Hybrid"; +"include"="Include"; "inputevent.none"="None"; "inputevent.up"="Up"; "inputevent.down"="Down"; From dcaeeeedc3adf2fdeb2fb230c0abdceff0070843 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 29 Dec 2022 16:45:06 -0800 Subject: [PATCH 05/40] Bump version --- Meshtastic.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index ab1f5b8b..21cd97b0 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1002,7 +1002,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.9; + MARKETING_VERSION = 2.0.10; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1035,7 +1035,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.9; + MARKETING_VERSION = 2.0.10; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; From 1b7a5017133d60189de7970dae5a370615afdf3b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 01:36:06 -0800 Subject: [PATCH 06/40] Localize parameterized strings for connect view bluetooth errors --- Meshtastic/Helpers/BLEManager.swift | 34 ++++++++++++++++------------- de.lproj/Localizable.strings | 2 ++ en.lproj/Localizable.strings | 2 ++ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 24cfae62..4ebfc0fb 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -110,7 +110,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { self.timeoutTimerRuns += 1 self.startScanning() } else { - MeshLogger.log("🚨 BLE Connecting 2 Second Timeout Timer Fired \(timeoutTimerCount) Time(s): \(name)") + print("🚨 BLE Connecting 2 Second Timeout Timer Fired \(timeoutTimerCount) Time(s): \(name)") } } @@ -206,26 +206,30 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let errorCode = (e as NSError).code if errorCode == 6 { // CBError.Code.connectionTimeout The connection has timed out unexpectedly. // Happens when device is manually reset / powered off - lastConnectionError = "🚨 \(e.localizedDescription) The app will automatically reconnect to the preferred radio if it come back in range." - MeshLogger.log("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") + lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ meshlog.ble.errorcode.6", + comment: "The app will automatically reconnect to the preferred radio if it come back in range."), + e.localizedDescription) + print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") } else if errorCode == 7 { // CBError.Code.peripheralDisconnected The specified device has disconnected from us. // Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work. - lastConnectionError = e.localizedDescription - MeshLogger.log("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") + lastConnectionError = "🚨 \(e.localizedDescription)" + print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") } else if errorCode == 14 { // Peer removed pairing information // Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that - lastConnectionError = "🚨 \(e.localizedDescription) This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio." - MeshLogger.log("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)") + lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ meshlog.ble.errorcode.14", + comment: "This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."), + e.localizedDescription) + print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)") } else { - lastConnectionError = e.localizedDescription - MeshLogger.log("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") + lastConnectionError = "🚨" + e.localizedDescription + print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") } } else { // Disconnected without error which indicates user intent to disconnect // Happens when swiping to disconnect - MeshLogger.log("ℹ️ BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect") + print("ℹ️ BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect") } // Start a scan so the disconnected peripheral is moved to the peripherals[] if it is awake self.startScanning() @@ -240,7 +244,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { for service in services { if service.uuid == meshtasticServiceCBUUID { peripheral.discoverCharacteristics([TORADIO_UUID, FROMRADIO_UUID, FROMNUM_UUID], for: service) - MeshLogger.log("✅ BLE Service for Meshtastic discovered by \(peripheral.name ?? "Unknown")") + print("✅ BLE Service for Meshtastic discovered by \(peripheral.name ?? "Unknown")") } } } @@ -249,7 +253,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { if let e = error { - MeshLogger.log("🚫 BLE Discover Characteristics error for \(peripheral.name ?? "Unknown") \(e) disconnecting device") + print("🚫 BLE Discover Characteristics error for \(peripheral.name ?? "Unknown") \(e) disconnecting device") // Try and stop crashes when this error occurs disconnectPeripheral() return @@ -261,16 +265,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { switch characteristic.uuid { case TORADIO_UUID: - MeshLogger.log("✅ BLE did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") + print("✅ BLE did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") TORADIO_characteristic = characteristic case FROMRADIO_UUID: - MeshLogger.log("✅ BLE did discover FROMRADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") + print("✅ BLE did discover FROMRADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") FROMRADIO_characteristic = characteristic peripheral.readValue(for: FROMRADIO_characteristic) case FROMNUM_UUID: - MeshLogger.log("✅ BLE did discover FROMNUM (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") + print("✅ BLE did discover FROMNUM (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") FROMNUM_characteristic = characteristic peripheral.setNotifyValue(true, for: characteristic) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 17586228..6e51b564 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -17,6 +17,8 @@ "available.radios"="Geräte in der Nähe"; "automatic.detection"="Automatische erkennung"; "ble.name"="BLE Name"; +"%@ ble.errorcode.6"=" %@! The app will automatically reconnect to the preferred radio if it come back in range."; +"%@ ble.errorcode.14"="%&!This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; "bluetooth"="Bluetooth"; "bluetooth.config"="Bluetooth Konfiguration"; "bluetooth.mode.randompin"="Zufällige PIN"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 8ce5133e..7cf8588c 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -107,6 +107,8 @@ "map"="Mesh Map"; "map.type"="Map Type"; "mesh.log"="Mesh Log"; +"%@ ble.errorcode.6"=" %@! The app will automatically reconnect to the preferred radio if it come back in range."; +"%@ ble.errorcode.14"="This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; "message"="Message"; "message.details"="Message Details"; "messages"="Messages"; From 6ea8c2627143c54f4ea032cf0ded8ef6f6cad162 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 01:52:33 -0800 Subject: [PATCH 07/40] Localize more BLE errors, print instead of mesh logging ble errors --- Meshtastic/Helpers/BLEManager.swift | 33 ++++++++++++----------------- de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 1 + 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 4ebfc0fb..8c9491ef 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -122,7 +122,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { self.lastConnectionError = "" } if connectedPeripheral != nil { - MeshLogger.log("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")") + print("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")") disconnectPeripheral() } centralManager?.connect(peripheral) @@ -135,7 +135,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let context = ["name": "\(peripheral.name ?? "Unknown")"] timeoutTimer = Timer.scheduledTimer(timeInterval: 1.5, target: self, selector: #selector(timeoutTimerFired), userInfo: context, repeats: true) RunLoop.current.add(timeoutTimer!, forMode: .common) - MeshLogger.log("ℹ️ BLE Connecting: \(peripheral.name ?? "Unknown")") + print("ℹ️ BLE Connecting: \(peripheral.name ?? "Unknown")") } // Disconnect Connected Peripheral @@ -186,13 +186,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } // Discover Services peripheral.discoverServices([meshtasticServiceCBUUID]) - MeshLogger.log("✅ BLE Connected: \(peripheral.name ?? "Unknown")") + print("✅ BLE Connected: \(peripheral.name ?? "Unknown")") } // Called when a Peripheral fails to connect func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { disconnectPeripheral() - MeshLogger.log("🚫 BLE Failed to Connect: \(peripheral.name ?? "Unknown")") + print("🚫 BLE Failed to Connect: \(peripheral.name ?? "Unknown")") } // Disconnect Peripheral Event @@ -369,7 +369,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { if let errorText = error?.localizedDescription { - MeshLogger.log("🚫 didUpdateNotificationStateFor error: \(errorText)") + print("🚫 didUpdateNotificationStateFor error: \(errorText)") } } @@ -382,16 +382,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let errorCode = (e as NSError).code - if errorCode == 5 { // CBATTErrorDomain Code=5 "Authentication is insufficient." - // BLE Pin connection error - lastConnectionError = "🚫 BLE \(e.localizedDescription) Please try connecting again and check the PIN carefully." - MeshLogger.log("🚫 BLE \(e.localizedDescription) Please try connecting again and check the PIN carefully.") - self.centralManager?.cancelPeripheralConnection(peripheral) - } - if errorCode == 15 { // CBATTErrorDomain Code=15 "Encryption is insufficient." - // BLE Pin connection error - lastConnectionError = "🚫 BLE \(e.localizedDescription) Please try connecting again and check the PIN carefully." - MeshLogger.log("🚫 BLE \(e.localizedDescription) Please try connecting again. You may need to forget the device under Settings > General > Bluetooth.") + if errorCode == 5 || errorCode == 15 { + // BLE PIN connection errors + // 5 CBATTErrorDomain Code=5 "Authentication is insufficient." + // 15 CBATTErrorDomain Code=15 "Encryption is insufficient." + lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ meshlog.ble.errorcode.pin", + comment: "Please try connecting again and check the PIN carefully."), + e.localizedDescription) + print("🚨 \(e.localizedDescription) Please try connecting again and check the PIN carefully.") self.centralManager?.cancelPeripheralConnection(peripheral) } } @@ -403,11 +401,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if characteristic.value == nil || characteristic.value!.isEmpty { return } - var decodedInfo = FromRadio() do { - decodedInfo = try FromRadio(serializedData: characteristic.value!) } catch { @@ -418,10 +414,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { // Handle Any local only packets we get over BLE case .unknownApp: - var nowKnown = false - // MyInfo + // MyInfo from initial connection if decodedInfo.myInfo.isInitialized && decodedInfo.myInfo.myNodeNum > 0 { let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".") diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 6e51b564..fedaa3a9 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -19,6 +19,7 @@ "ble.name"="BLE Name"; "%@ ble.errorcode.6"=" %@! The app will automatically reconnect to the preferred radio if it come back in range."; "%@ ble.errorcode.14"="%&!This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; +"%@ ble.errorcode.pin"=" %@! Please try connecting again and check the PIN carefully."; "bluetooth"="Bluetooth"; "bluetooth.config"="Bluetooth Konfiguration"; "bluetooth.mode.randompin"="Zufällige PIN"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 7cf8588c..e0245a14 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -109,6 +109,7 @@ "mesh.log"="Mesh Log"; "%@ ble.errorcode.6"=" %@! The app will automatically reconnect to the preferred radio if it come back in range."; "%@ ble.errorcode.14"="This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; +"%@ ble.errorcode.pin"=" %@! Please try connecting again and check the PIN carefully."; "message"="Message"; "message.details"="Message Details"; "messages"="Messages"; From 3b8b5ff7d87ee53777055dd83ff4999a699d9839 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 02:10:14 -0800 Subject: [PATCH 08/40] More ble error cleanup --- Meshtastic/Helpers/BLEManager.swift | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 8c9491ef..420efb92 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -565,7 +565,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } catch { - MeshLogger.log("💥 Error Deleting the All - Broadcast User") + print("💥 Error Deleting the All - Broadcast User") } if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce { @@ -573,7 +573,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { lastConnectionError = "" timeoutTimerRuns = 0 isSubscribed = true - MeshLogger.log("🤜 BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)") + MeshLogger.log("🤜 Want Config Request Complete. Packet Id: \(decodedInfo.configCompleteID)") peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) // Config conplete returns so we don't read the characteristic again // MARK: Share Location Position Update Timer @@ -744,7 +744,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - MeshLogger.log("📍 Sent a Waypoint Packet from the Apple device GPS to node: \(fromNodeNum)") + MeshLogger.log("📍 Sent a Waypoint Packet from: \(fromNodeNum)") if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) @@ -1417,16 +1417,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - meshPacket.decoded = dataMessage let messageDescription = "Saved External Notification Module Config for \(toUser.longName ?? "Unknown")" - if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { - return Int64(meshPacket.id) } - return 0 } @@ -1503,16 +1499,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - meshPacket.decoded = dataMessage let messageDescription = "Saved Serial Module Config for \(toUser.longName ?? "Unknown")" - if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { - return Int64(meshPacket.id) } - return 0 } @@ -1531,16 +1523,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - meshPacket.decoded = dataMessage let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "Unknown")" - if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { - return Int64(meshPacket.id) } - return 0 } @@ -1550,11 +1538,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket - let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { - let newMessage = MessageEntity(context: context!) newMessage.messageId = Int64(meshPacket.id) newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970) @@ -1565,17 +1551,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { newMessage.toUser = toUser do { - connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) try context!.save() MeshLogger.log("💾 \(adminDescription)") - return true - } catch { - context!.rollback() - let nsError = error as NSError MeshLogger.log("💥 Error inserting new core data MessageEntity: \(nsError)") } @@ -1624,7 +1605,7 @@ extension BLEManager: CBCentralManagerDelegate { if timeoutTimerRuns < 2 && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" { self.connectTo(peripheral: peripheral) - MeshLogger.log("ℹ️ BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")") + print("ℹ️ BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")") } let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String let device = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: name ?? "Unknown", shortName: "????", longName: name ?? "Unknown", firmwareVersion: "Unknown", rssi: RSSI.intValue, lastUpdate: Date(), peripheral: peripheral) From 5501574530a79869282e1427aef5552854b13212 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 09:35:05 -0800 Subject: [PATCH 09/40] More cleanup --- Meshtastic.xcodeproj/project.pbxproj | 4 -- Meshtastic/Helpers/BLEManager.swift | 23 +++---- Meshtastic/Helpers/MeshPackets.swift | 7 -- .../Views/Bluetooth/InvalidVersion.swift | 2 +- Meshtastic/Views/Helpers/MessageBubble.swift | 64 ------------------- Meshtastic/Views/Helpers/NodeAnnotation.swift | 2 +- Meshtastic/Views/Settings/MeshLog.swift | 5 +- de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 1 + 9 files changed, 14 insertions(+), 95 deletions(-) delete mode 100644 Meshtastic/Views/Helpers/MessageBubble.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 21cd97b0..39ea2178 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -36,7 +36,6 @@ DD457188293C7E63000C49FB /* SignalStrengthIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD457187293C7E63000C49FB /* SignalStrengthIndicator.swift */; }; DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; }; DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; }; - DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D826F3093800029299 /* MessageBubble.swift */; }; DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; }; DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158B2824A91E0032668E /* module_config.pb.swift */; }; DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158D2824AA7E0032668E /* config.pb.swift */; }; @@ -155,7 +154,6 @@ DD457BC4295D5E35004BCE4D /* MeshtasticDataModelV5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModelV5.xcdatamodel; sourceTree = ""; }; DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = ""; }; DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = ""; }; - DD47E3D826F3093800029299 /* MessageBubble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBubble.swift; sourceTree = ""; }; DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; DD4C158B2824A91E0032668E /* module_config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = module_config.pb.swift; sourceTree = ""; }; DD4C158D2824AA7E0032668E /* config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = config.pb.swift; sourceTree = ""; }; @@ -521,7 +519,6 @@ isa = PBXGroup; children = ( DD47E3D526F17ED900029299 /* CircleText.swift */, - DD47E3D826F3093800029299 /* MessageBubble.swift */, DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, DDC3B273283F411B00AC321C /* LastHeardText.swift */, DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */, @@ -796,7 +793,6 @@ DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */, DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */, - DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 420efb92..9b325cd9 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -206,7 +206,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let errorCode = (e as NSError).code if errorCode == 6 { // CBError.Code.connectionTimeout The connection has timed out unexpectedly. // Happens when device is manually reset / powered off - lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ meshlog.ble.errorcode.6", + lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ ble.errorcode.6", comment: "The app will automatically reconnect to the preferred radio if it come back in range."), e.localizedDescription) print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") @@ -217,7 +217,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } else if errorCode == 14 { // Peer removed pairing information // Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that - lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ meshlog.ble.errorcode.14", + lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ ble.errorcode.14", comment: "This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."), e.localizedDescription) print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)") @@ -288,31 +288,25 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } func requestDeviceMetadata() { + guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return } MeshLogger.log("ℹ️ Requesting Device Metadata for \(connectedPeripheral!.peripheral.name ?? "Unknown")") - var adminPacket = AdminMessage() adminPacket.getDeviceMetadataRequest = true - var meshPacket: MeshPacket = MeshPacket() meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Date: Fri, 30 Dec 2022 11:08:59 -0800 Subject: [PATCH 10/40] Localize config save confirm --- Meshtastic.xcodeproj/project.pbxproj | 2 +- .../Settings/Config/BluetoothConfig.swift | 148 +++++---- .../Views/Settings/Config/DeviceConfig.swift | 8 +- .../Views/Settings/Config/DisplayConfig.swift | 184 ++++++----- .../Views/Settings/Config/LoRaConfig.swift | 4 +- .../Config/Module/CannedMessagesConfig.swift | 3 + .../Module/ExternalNotificationConfig.swift | 3 + .../Settings/Config/Module/MQTTConfig.swift | 287 +++++++++--------- .../Config/Module/RangeTestConfig.swift | 3 + .../Settings/Config/Module/SerialConfig.swift | 4 +- .../Config/Module/TelemetryConfig.swift | 4 +- .../Views/Settings/Config/NetworkConfig.swift | 4 +- .../Settings/Config/PositionConfig.swift | 3 + Meshtastic/Views/Settings/UserConfig.swift | 4 +- de.lproj/Localizable.strings | 2 + en.lproj/Localizable.strings | 2 + 16 files changed, 334 insertions(+), 331 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 39ea2178..7380d000 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -339,10 +339,10 @@ children = ( DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */, DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */, + DD2160AE28C5552500C17253 /* MQTTConfig.swift */, DD41582928585C32009B0E59 /* RangeTestConfig.swift */, DD6193782863875F00E59241 /* SerialConfig.swift */, DD415827285859C4009B0E59 /* TelemetryConfig.swift */, - DD2160AE28C5552500C17253 /* MQTTConfig.swift */, ); path = Module; sourceTree = ""; diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 1516a5a9..57e2da92 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -33,89 +33,81 @@ struct BluetoothConfig: View { var body: some View { - VStack { - - Form { - - Section(header: Text("options")) { - - Toggle(isOn: $enabled) { - - Label("enabled", systemImage: "antenna.radiowaves.left.and.right") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - - - Picker("Pairing Mode", selection: $mode ) { - ForEach(BluetoothModes.allCases) { bm in - Text(bm.description) - } - } - .pickerStyle(DefaultPickerStyle()) - - if mode == 1 { - - HStack { - Label("Fixed PIN", systemImage: "wallet.pass") - TextField("Fixed PIN", text: $fixedPin) - .foregroundColor(.gray) - .onChange(of: fixedPin, perform: { value in - //Require that pin is no more than 6 numbers and no less than 6 numbers - if fixedPin.utf8.count == pinLength { - shortPin = false - } else if fixedPin.utf8.count > pinLength { - shortPin = false - fixedPin = String(fixedPin.prefix(pinLength)) - } else if fixedPin.utf8.count < pinLength { - shortPin = true - } - }) - .foregroundColor(.gray) - } - .keyboardType(.decimalPad) - if shortPin { - - Text("BLE Pin must be 6 digits long.") - .font(.callout) - .foregroundColor(.red) - } - } - } - } - .disabled(bleManager.connectedPeripheral == nil) + Form { + Section(header: Text("options")) { - Button { - isPresentingSaveConfirm = true - } label: { - Label("save", systemImage: "square.and.arrow.down") - } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges || shortPin) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "Are you sure you want to save?", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")") { - var bc = Config.BluetoothConfig() - bc.enabled = enabled - bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin - bc.fixedPin = UInt32(fixedPin) ?? 123456 - let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: node!.user!, toUser: node!.user!) - if adminMessageId > 0 { - // Should show a saved successfully alert once I know that to be true - // for now just disable the button after a successful save - hasChanges = false - goBack() + Toggle(isOn: $enabled) { + Label("enabled", systemImage: "antenna.radiowaves.left.and.right") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Picker("bluetooth.pairingmode", selection: $mode ) { + ForEach(BluetoothModes.allCases) { bm in + Text(bm.description) + } + } + .pickerStyle(DefaultPickerStyle()) + + if mode == 1 { + HStack { + Label("bluetooth.mode.fixedpin", systemImage: "wallet.pass") + TextField("bluetooth.mode.fixedpin", text: $fixedPin) + .foregroundColor(.gray) + .onChange(of: fixedPin, perform: { value in + //Require that pin is no more than 6 numbers and no less than 6 numbers + if fixedPin.utf8.count == pinLength { + shortPin = false + } else if fixedPin.utf8.count > pinLength { + shortPin = false + fixedPin = String(fixedPin.prefix(pinLength)) + } else if fixedPin.utf8.count < pinLength { + shortPin = true + } + }) + .foregroundColor(.gray) + } + .keyboardType(.decimalPad) + if shortPin { + Text("BLE Pin must be 6 digits long.") + .font(.callout) + .foregroundColor(.red) } } - } message: { - Text("After bluetooth config saves the node will reboot.") } } + .disabled(bleManager.connectedPeripheral == nil) + + Button { + isPresentingSaveConfirm = true + } label: { + Label("save", systemImage: "square.and.arrow.down") + } + .disabled(bleManager.connectedPeripheral == nil || !hasChanges || shortPin) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "are.you.sure", + isPresented: $isPresentingSaveConfirm, + titleVisibility: .visible + ) { + Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")") { + var bc = Config.BluetoothConfig() + bc.enabled = enabled + bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin + bc.fixedPin = UInt32(fixedPin) ?? 123456 + let adminMessageId = bleManager.saveBluetoothConfig(config: bc, fromUser: node!.user!, toUser: node!.user!) + if adminMessageId > 0 { + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + goBack() + } + } + } message: { + Text("config.save.confirm") + } .navigationTitle("bluetooth.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 1abc2cdf..7a06d1c2 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -152,7 +152,7 @@ struct DeviceConfig: View { .padding() .confirmationDialog( - "Are you sure you want to save?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { @@ -175,18 +175,14 @@ struct DeviceConfig: View { } } message: { - - Text("After device config saves the node will reboot.") + Text("config.save.confirm") } } Spacer() } - .navigationTitle("device.config") .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) .onAppear { diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 13b12138..dfda6988 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -27,110 +27,108 @@ struct DisplayConfig: View { var body: some View { - VStack { - - Form { - Section(header: Text("Device Screen")) { - - Picker("Screen on for", selection: $screenOnSeconds ) { - ForEach(ScreenOnIntervals.allCases) { soi in - Text(soi.description) - } + Form { + Section(header: Text("Device Screen")) { + + Picker("Screen on for", selection: $screenOnSeconds ) { + ForEach(ScreenOnIntervals.allCases) { soi in + Text(soi.description) } - .pickerStyle(DefaultPickerStyle()) - - Text("How long the screen remains on after the user button is pressed or messages are received.") - .font(.caption) - - Picker("Carousel Interval", selection: $screenCarouselInterval ) { - ForEach(ScreenCarouselIntervals.allCases) { sci in - Text(sci.description) - } - } - .pickerStyle(DefaultPickerStyle()) - Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.") - .font(.caption) - - Toggle(isOn: $compassNorthTop) { - - Label("Always point north", systemImage: "location.north.circle") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Text("The compass heading on the screen outside of the circle will always point north.") - .font(.caption) - - Toggle(isOn: $flipScreen) { - - Label("Flip Screen", systemImage: "pip.swap") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Text("Flip screen vertically") - .font(.caption) - Picker("OLED Type", selection: $oledType ) { - ForEach(OledTypes.allCases) { ot in - Text(ot.description) - } - } - .pickerStyle(DefaultPickerStyle()) - Text("Override automatic OLED screen detection.") - .font(.caption) - } - Section(header: Text("Format")) { - Picker("GPS Format", selection: $gpsFormat ) { - ForEach(GpsFormats.allCases) { lu in - Text(lu.description) - } + .pickerStyle(DefaultPickerStyle()) + + Text("How long the screen remains on after the user button is pressed or messages are received.") + .font(.caption) + + Picker("Carousel Interval", selection: $screenCarouselInterval ) { + ForEach(ScreenCarouselIntervals.allCases) { sci in + Text(sci.description) } - .pickerStyle(DefaultPickerStyle()) - - Text("The format used to display GPS coordinates on the device screen.") - .font(.caption) - .listRowSeparator(.visible) } + .pickerStyle(DefaultPickerStyle()) + Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.") + .font(.caption) + + Toggle(isOn: $compassNorthTop) { + + Label("Always point north", systemImage: "location.north.circle") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("The compass heading on the screen outside of the circle will always point north.") + .font(.caption) + + Toggle(isOn: $flipScreen) { + + Label("Flip Screen", systemImage: "pip.swap") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + Text("Flip screen vertically") + .font(.caption) + Picker("OLED Type", selection: $oledType ) { + ForEach(OledTypes.allCases) { ot in + Text(ot.description) + } + } + .pickerStyle(DefaultPickerStyle()) + Text("Override automatic OLED screen detection.") + .font(.caption) + } - .disabled(bleManager.connectedPeripheral == nil) - - Button { - - isPresentingSaveConfirm = true + Section(header: Text("Format")) { + Picker("GPS Format", selection: $gpsFormat ) { + ForEach(GpsFormats.allCases) { lu in + Text(lu.description) + } + } + .pickerStyle(DefaultPickerStyle()) - } label: { - - Label("save", systemImage: "square.and.arrow.down") + Text("The format used to display GPS coordinates on the device screen.") + .font(.caption) + .listRowSeparator(.visible) } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - - "are.you.sure", - isPresented: $isPresentingSaveConfirm - ) { - Button("Save Display Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - - var dc = Config.DisplayConfig() - dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() - dc.screenOnSecs = UInt32(screenOnSeconds) - dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval) - dc.compassNorthTop = compassNorthTop - dc.flipScreen = flipScreen - dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue() - - let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node!.user!, toUser: node!.user!) - - if adminMessageId > 0 { + } + .disabled(bleManager.connectedPeripheral == nil) + + Button { - // 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() - } + 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 + ) { + Button("Save Display Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + + var dc = Config.DisplayConfig() + dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() + dc.screenOnSecs = UInt32(screenOnSeconds) + dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval) + dc.compassNorthTop = compassNorthTop + dc.flipScreen = flipScreen + dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue() + + let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node!.user!, toUser: node!.user!) + if adminMessageId > 0 { + + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + goBack() } } } + message: { + Text("config.save.confirm") + } .navigationTitle("display.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 6f5164a3..ed618aff 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -71,7 +71,7 @@ struct LoRaConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure you want to save?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { @@ -91,7 +91,7 @@ struct LoRaConfig: View { } } } message: { - Text("After LoRa config saves the node will reboot.") + Text("config.save.confirm") } } .navigationTitle("lora.config") diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 65922a38..db08f329 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -269,6 +269,9 @@ struct CannedMessagesConfig: View { } } } + message: { + Text("config.save.confirm") + } .navigationTitle("canned.messages.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index c3a9b9fe..e1bb039e 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -213,6 +213,9 @@ struct ExternalNotificationConfig: View { } } } + message: { + Text("config.save.confirm") + } .navigationTitle("external.notification.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 89f472dc..aa91813d 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -22,158 +22,155 @@ struct MQTTConfig: View { @State var jsonEnabled = false var body: some View { - - VStack { - - Form { - Section(header: Text("options")) { + + Form { + Section(header: Text("options")) { + + Toggle(isOn: $enabled) { + + Label("enabled", systemImage: "dot.radiowaves.right") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $encryptionEnabled) { + + Label("Encryption Enabled", systemImage: "lock.icloud") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + Toggle(isOn: $jsonEnabled) { + + Label("JSON Enabled", systemImage: "ellipsis.curlybraces") + } + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + + } + Section(header: Text("Custom Server")) { + + HStack { + Label("Address", systemImage: "server.rack") + TextField("Server Address", text: $address) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: address, perform: { value in + + let totalBytes = address.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 30 { + + let firstNBytes = Data(username.utf8.prefix(30)) - Toggle(isOn: $enabled) { - - Label("enabled", systemImage: "dot.radiowaves.right") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - - Toggle(isOn: $encryptionEnabled) { - - Label("Encryption Enabled", systemImage: "lock.icloud") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - - Toggle(isOn: $jsonEnabled) { - - Label("JSON Enabled", systemImage: "ellipsis.curlybraces") - } - .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + address = maxBytesString + } + } + hasChanges = true + }) + .foregroundColor(.gray) + .keyboardType(.default) } - Section(header: Text("Custom Server")) { - - HStack { - Label("Address", systemImage: "server.rack") - TextField("Server Address", text: $address) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: address, perform: { value in - - let totalBytes = address.utf8.count - - // Only mess with the value if it is too big - if totalBytes > 30 { - - let firstNBytes = Data(username.utf8.prefix(30)) - - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - - // Set the shortName back to the last place where it was the right size - address = maxBytesString - } - } - hasChanges = true - }) - .foregroundColor(.gray) - .keyboardType(.default) - } - .autocorrectionDisabled() - - HStack { - Label("mqtt.username", systemImage: "person.text.rectangle") - TextField("mqtt.username", text: $username) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: username, perform: { value in - - let totalBytes = username.utf8.count - - // Only mess with the value if it is too big - if totalBytes > 62 { - - let firstNBytes = Data(username.utf8.prefix(62)) - - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - - // Set the shortName back to the last place where it was the right size - username = maxBytesString - } - } - hasChanges = true - }) - .foregroundColor(.gray) - } - .keyboardType(.default) - .scrollDismissesKeyboard(.interactively) - HStack { - Label("password", systemImage: "wallet.pass") - TextField("password", text: $password) - .foregroundColor(.gray) - .autocapitalization(.none) - .disableAutocorrection(true) - .onChange(of: password, perform: { value in - - let totalBytes = password.utf8.count - - // Only mess with the value if it is too big - if totalBytes > 62 { - - let firstNBytes = Data(password.utf8.prefix(62)) - - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - - // Set the shortName back to the last place where it was the right size - password = maxBytesString - } - } - hasChanges = true - }) - .foregroundColor(.gray) - } - .keyboardType(.default) - .scrollDismissesKeyboard(.interactively) - } - Text("WiFi or Ethernet must also be enabled for MQTT to work. You can set uplink and downlink for each channel.") - .font(.callout) - } - .scrollDismissesKeyboard(.interactively) - .disabled(!(node != nil)) - - Button { - - isPresentingSaveConfirm = true + .autocorrectionDisabled() - } label: { - - Label("save", systemImage: "square.and.arrow.down") + HStack { + Label("mqtt.username", systemImage: "person.text.rectangle") + TextField("mqtt.username", text: $username) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: username, perform: { value in + + let totalBytes = username.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 62 { + + let firstNBytes = Data(username.utf8.prefix(62)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + username = maxBytesString + } + } + hasChanges = true + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) + HStack { + Label("password", systemImage: "wallet.pass") + TextField("password", text: $password) + .foregroundColor(.gray) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: password, perform: { value in + + let totalBytes = password.utf8.count + + // Only mess with the value if it is too big + if totalBytes > 62 { + + let firstNBytes = Data(password.utf8.prefix(62)) + + if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { + + // Set the shortName back to the last place where it was the right size + password = maxBytesString + } + } + hasChanges = true + }) + .foregroundColor(.gray) + } + .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) } - .disabled(bleManager.connectedPeripheral == nil || !hasChanges) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - Button("Save MQTT Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - var mqtt = ModuleConfig.MQTTConfig() - mqtt.enabled = self.enabled - mqtt.address = self.address - mqtt.username = self.username - mqtt.password = self.password - mqtt.encryptionEnabled = self.encryptionEnabled - mqtt.jsonEnabled = self.jsonEnabled - let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: node!.user!, toUser: node!.user!) - if adminMessageId > 0 { - // Should show a saved successfully alert once I know that to be true - // for now just disable the button after a successful save - hasChanges = false - goBack() - } + Text("WiFi or Ethernet must also be enabled for MQTT to work. You can set uplink and downlink for each channel.") + .font(.callout) + } + .scrollDismissesKeyboard(.interactively) + .disabled(!(node != 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 + ) { + Button("Save MQTT Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + var mqtt = ModuleConfig.MQTTConfig() + mqtt.enabled = self.enabled + mqtt.address = self.address + mqtt.username = self.username + mqtt.password = self.password + mqtt.encryptionEnabled = self.encryptionEnabled + mqtt.jsonEnabled = self.jsonEnabled + let adminMessageId = bleManager.saveMQTTConfig(config: mqtt, fromUser: node!.user!, toUser: node!.user!) + if adminMessageId > 0 { + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save + hasChanges = false + goBack() } } } + message: { + Text("config.save.confirm") + } .navigationTitle("mqtt.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 38ecae08..8e6a6438 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -114,6 +114,9 @@ struct RangeTestConfig: View { } } } + message: { + Text("config.save.confirm") + } .navigationTitle("range.test.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index 349373bd..d86fca71 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -147,7 +147,9 @@ struct SerialConfig: View { } } } - + message: { + Text("config.save.confirm") + } .navigationTitle("serial.config") .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 0eeb1902..148b9981 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -153,7 +153,9 @@ struct TelemetryConfig: View { } } } - + message: { + Text("config.save.confirm") + } .navigationTitle("telemetry.config") .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index c71bfaa0..58c022f0 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -104,7 +104,7 @@ struct NetworkConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure you want to save?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { @@ -125,7 +125,7 @@ struct NetworkConfig: View { } } } message: { - Text("After network config saves the node will reboot.") + Text("config.save.confirm") } } .navigationTitle("network.config") diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index e800a64f..6006109f 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -239,6 +239,9 @@ struct PositionConfig: View { } } } + message: { + Text("config.save.confirm") + } } .navigationTitle("position.config") .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Settings/UserConfig.swift b/Meshtastic/Views/Settings/UserConfig.swift index 6380430b..f372499e 100644 --- a/Meshtastic/Views/Settings/UserConfig.swift +++ b/Meshtastic/Views/Settings/UserConfig.swift @@ -80,7 +80,7 @@ struct UserConfig: View { .controlSize(.large) .padding() .confirmationDialog( - "Are you sure you want to save?", + "are.you.sure", isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { @@ -95,7 +95,7 @@ struct UserConfig: View { } } } message: { - Text("After user config saves the node will reboot.") + Text("config.save.confirm") } } Spacer() diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index fa78bc25..06a62a0e 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -25,6 +25,7 @@ "bluetooth.mode.randompin"="Zufällige PIN"; "bluetooth.mode.fixedpin"="Feste PIN"; "bluetooth.mode.nopin"="Keine PIN (geht einfach)"; +"bluetooth.pairingmode"="Pairing Mode"; "bytes"="Bytes"; "cancel"="Abbrechen"; "canned.messages"="Canned Messages"; @@ -39,6 +40,7 @@ "channels"="Kanäle"; "clear.app.data"="App Daten löschen"; "close"="Close"; +"config.save.confirm"="After config values save the node will reboot."; "connected.radio"="Verbundenes Gerät"; "communicating"="Verbinde mit Gerät..."; "connected"="Derzeit verbunden"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 1f312eee..db240f32 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -22,6 +22,7 @@ "bluetooth.mode.randompin"="Random PIN"; "bluetooth.mode.fixedpin"="Fixed PIN"; "bluetooth.mode.nopin"="No PIN (Just Works)"; +"bluetooth.pairingmode"="Pairing Mode"; "bytes"="Bytes"; "cancel"="Cancel"; "canned.messages"="Canned Messages"; @@ -36,6 +37,7 @@ "channels"="Channels"; "clear.app.data"="Clear App Data"; "close"="Close"; +"config.save.confirm"="After config values save the node will reboot."; "connected.radio"="Connected Radio"; "communicating"="Communicating with device. ."; "connected"="Currently Connected"; From 59be21e38cdf0a6c5ab386d4b8fa37d553e886f7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 13:18:02 -0800 Subject: [PATCH 11/40] More localization --- Meshtastic.xcodeproj/project.pbxproj | 8 ++ Meshtastic/Enums/AppSettingsEnums.swift | 93 +++++++++++++++++++ Meshtastic/Enums/PositionConfigEnums.swift | 46 --------- Meshtastic/Enums/UpdateIntervals.swift | 78 ++++++++++++++++ .../Map/Custom/PositionAnnotationView.swift | 7 +- Meshtastic/Views/Nodes/NodeMap.swift | 8 +- Meshtastic/Views/Settings/AppSettings.swift | 85 ----------------- Meshtastic/Views/Settings/Channels.swift | 1 - .../Config/Module/TelemetryConfig.swift | 60 ------------ .../Settings/Config/PositionConfig.swift | 2 +- de.lproj/Localizable.strings | 8 ++ en.lproj/Localizable.strings | 8 ++ 12 files changed, 200 insertions(+), 204 deletions(-) create mode 100644 Meshtastic/Enums/AppSettingsEnums.swift create mode 100644 Meshtastic/Enums/UpdateIntervals.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 7380d000..5b5fcca8 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -68,6 +68,7 @@ DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; }; DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; }; + DD994B69295F88B60013760A /* UpdateIntervals.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* UpdateIntervals.swift */; }; DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; }; DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; }; DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; }; @@ -104,6 +105,7 @@ DDD3BBD5292D763200D609B3 /* MeshtasticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; }; + DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; /* End PBXBuildFile section */ @@ -187,6 +189,7 @@ DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = ""; }; DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = ""; }; + DD994B68295F88B60013760A /* UpdateIntervals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateIntervals.swift; sourceTree = ""; }; DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; @@ -231,6 +234,7 @@ DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; 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 = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -360,6 +364,7 @@ DD8ED9C6289CE4A100B3B0AB /* Enums */ = { isa = PBXGroup; children = ( + DDE0F7C4295F77B700B8AAB3 /* AppSettingsEnums.swift */, DDB6ABD828B0A4BA00384BA1 /* BluetoothModes.swift */, DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */, DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */, @@ -371,6 +376,7 @@ DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */, DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */, DD1925B828CDA93900720036 /* SerialConfigEnums.swift */, + DD994B68295F88B60013760A /* UpdateIntervals.swift */, ); path = Enums; sourceTree = ""; @@ -753,6 +759,7 @@ DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */, DD1BF2F92776FE2E008C8D2F /* UserMessageList.swift in Sources */, DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */, + DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */, DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */, DDB2CC6E27F3EB47009C5FCC /* telemetry.pb.swift in Sources */, DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */, @@ -794,6 +801,7 @@ C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */, DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */, DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, + DD994B69295F88B60013760A /* UpdateIntervals.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, diff --git a/Meshtastic/Enums/AppSettingsEnums.swift b/Meshtastic/Enums/AppSettingsEnums.swift new file mode 100644 index 00000000..f94a9b6c --- /dev/null +++ b/Meshtastic/Enums/AppSettingsEnums.swift @@ -0,0 +1,93 @@ +// +// AppSettingsEnums.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 12/30/22. +// + +import Foundation + +enum KeyboardType: Int, CaseIterable, Identifiable { + + case defaultKeyboard = 0 + case asciiCapable = 1 + case twitter = 9 + case emailAddress = 7 + case numbersAndPunctuation = 2 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .defaultKeyboard: + return NSLocalizedString("default", comment: "Default Keyboard") + case .asciiCapable: + return NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard") + case .twitter: + return NSLocalizedString("twitter", comment: "Twitter Keyboard") + case .emailAddress: + return NSLocalizedString("email.address", comment: "Email Address Keyboard") + case .numbersAndPunctuation: + return NSLocalizedString("numbers.punctuation", comment: "Numbers and Punctuation Keyboard") + } + } + } +} + +enum MeshMapType: String, CaseIterable, Identifiable { + + case satellite = "satellite" + case hybrid = "hybrid" + case standard = "standard" + + var id: String { self.rawValue } + + var description: String { + get { + switch self { + case .satellite: + return NSLocalizedString("satellite", comment: "Satellite Map Type") + case .standard: + return NSLocalizedString("standard", comment: "Standard Map Type") + case .hybrid: + return NSLocalizedString("hybrid", comment: "Hybrid Map Type") + } + } + } +} + +enum LocationUpdateInterval: Int, CaseIterable, Identifiable { + + case fiveSeconds = 5 + case tenSeconds = 10 + case fifteenSeconds = 15 + case thirtySeconds = 30 + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .fiveSeconds: + return NSLocalizedString("interval.five.seconds", comment: "Five Seconds") + case .tenSeconds: + return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds") + case .fifteenSeconds: + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") + case .thirtySeconds: + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") + case .oneMinute: + return NSLocalizedString("interval.one.minute", comment: "One Minute") + case .fiveMinutes: + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") + case .tenMinutes: + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") + case .fifteenMinutes: + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") + } + } + } +} diff --git a/Meshtastic/Enums/PositionConfigEnums.swift b/Meshtastic/Enums/PositionConfigEnums.swift index 081420f0..e353e88f 100644 --- a/Meshtastic/Enums/PositionConfigEnums.swift +++ b/Meshtastic/Enums/PositionConfigEnums.swift @@ -7,52 +7,6 @@ import Foundation -enum PositionBroadcastIntervals: Int, CaseIterable, Identifiable { - - case fifteenSeconds = 15 - case thirtySeconds = 30 - case oneMinute = 60 - case fiveMinutes = 300 - case tenMinutes = 600 - case fifteenMinutes = 900 - case thirtyMinutes = 1800 - case oneHour = 3600 - case sixHours = 21600 - case twelveHours = 43200 - case twentyFourHours = 86400 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - - case .fifteenSeconds: - return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") - case .thirtySeconds: - return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") - case .oneMinute: - return NSLocalizedString("interval.one.minute", comment: "One Minute") - case .fiveMinutes: - return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") - case .tenMinutes: - return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") - case .fifteenMinutes: - return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") - case .thirtyMinutes: - return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes") - case .oneHour: - return NSLocalizedString("interval.one.hour", comment: "One Hour") - case .sixHours: - return NSLocalizedString("interval.six.hours", comment: "Six Hours") - case .twelveHours: - return NSLocalizedString("interval.twelve.hours", comment: "Twelve Hours") - case .twentyFourHours: - return NSLocalizedString("interval.twentyfour.hours", comment: "Twenty Four Hours") - } - } - } -} - enum GpsFormats: Int, CaseIterable, Identifiable { case gpsFormatDec = 0 diff --git a/Meshtastic/Enums/UpdateIntervals.swift b/Meshtastic/Enums/UpdateIntervals.swift new file mode 100644 index 00000000..05524eed --- /dev/null +++ b/Meshtastic/Enums/UpdateIntervals.swift @@ -0,0 +1,78 @@ +// +// UpdateIntervals.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 12/30/22. +// + +import Foundation + +enum UpdateIntervals: Int, CaseIterable, Identifiable { + + case fifteenSeconds = 15 + case thirtySeconds = 30 + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 + case thirtyMinutes = 1800 + case oneHour = 3600 + case twoHours = 7200 + case threeHours = 10800 + case fourHours = 14400 + case fiveHours = 18000 + case sixHours = 21600 + case twelveHours = 43200 + case eighteenHours = 64800 + case twentyFourHours = 86400 + case thirtySixHours = 129600 + case fortyeightHours = 172800 + case seventyTwoHours = 259200 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + + case .fifteenSeconds: + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") + case .thirtySeconds: + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") + case .oneMinute: + return NSLocalizedString("interval.one.minute", comment: "One Minute") + case .fiveMinutes: + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") + case .tenMinutes: + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") + case .fifteenMinutes: + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") + case .thirtyMinutes: + return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes") + case .oneHour: + return NSLocalizedString("interval.one.hour", comment: "One Hour") + case .twoHours: + return NSLocalizedString("interval.two.hours", comment: "Two Hours") + case .threeHours: + return NSLocalizedString("interval.three.hours", comment: "Three Hours") + case .fourHours: + return NSLocalizedString("interval.four.hours", comment: "Four Hours") + case .fiveHours: + return NSLocalizedString("interval.five.hours", comment: "Five Hours") + case .sixHours: + return NSLocalizedString("interval.six.hours", comment: "Six Hours") + case .twelveHours: + return NSLocalizedString("interval.twelve.hours", comment: "Twelve Hours") + case .eighteenHours: + return NSLocalizedString("interval.eighteen.hours", comment: "Eighteen Hours") + case .twentyFourHours: + return NSLocalizedString("interval.twentyfour.hours", comment: "Twenty Four Hours") + case .thirtySixHours: + return NSLocalizedString("interval.thirtysix.hours", comment: "Thirty Six Hours") + case .fortyeightHours: + return NSLocalizedString("interval.fortyeight.hours", comment: "Forty Eight Hours") + case .seventyTwoHours: + return NSLocalizedString("interval.seventytwo.hours", comment: "Seventy Two Hours") + } + } + } +} diff --git a/Meshtastic/Views/Map/Custom/PositionAnnotationView.swift b/Meshtastic/Views/Map/Custom/PositionAnnotationView.swift index 47c20197..3c3e0f79 100644 --- a/Meshtastic/Views/Map/Custom/PositionAnnotationView.swift +++ b/Meshtastic/Views/Map/Custom/PositionAnnotationView.swift @@ -7,6 +7,7 @@ import UIKit import MapKit +import SwiftUI // a simple circle annotation, with a string in it class PositionAnnotation: NSObject, MKAnnotation { @@ -53,11 +54,7 @@ class PositionAnnotationView: MKAnnotationView { guard let context = UIGraphicsGetCurrentContext() else { return } let circleRect = CGRect(x: 1, y: 1, width: 38, height: 38) - - context.setFillColor(CGColor(red: 0, green: 0.5, blue: 1.0, alpha: 1.0)) - + context.setFillColor(Color.accentColor.cgColor ?? CGColor(red: 0, green: 0.5, blue: 1.0, alpha: 1.0)) context.fillEllipse(in: circleRect) - } - } diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index 9442e16c..3f968bab 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -84,8 +84,6 @@ struct NodeMap: View { var body: some View { - //self.$userLocation = LocationHelper.currentLocation - NavigationStack { ZStack { @@ -112,11 +110,9 @@ struct NodeMap: View { ) .frame(maxHeight: .infinity) - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - } + .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) + } } - .navigationTitle("Mesh Map") - .navigationBarTitleDisplayMode(.inline) .navigationBarItems(leading: MeshtasticLogo(), trailing: diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 7a1cd178..a9d9ada1 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -4,91 +4,6 @@ import SwiftUI import SwiftProtobuf import MapKit -enum KeyboardType: Int, CaseIterable, Identifiable { - - case defaultKeyboard = 0 - case asciiCapable = 1 - case twitter = 9 - case emailAddress = 7 - case numbersAndPunctuation = 2 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .defaultKeyboard: - return NSLocalizedString("default", comment: "Default Keyboard") - case .asciiCapable: - return NSLocalizedString("ascii.capable", comment: "ASCII Capable Keyboard") - case .twitter: - return NSLocalizedString("twitter", comment: "Twitter Keyboard") - case .emailAddress: - return NSLocalizedString("email.address", comment: "Email Address Keyboard") - case .numbersAndPunctuation: - return NSLocalizedString("numbers.punctuation", comment: "Numbers and Punctuation Keyboard") - } - } - } -} - -enum MeshMapType: String, CaseIterable, Identifiable { - - case satellite = "satellite" - case hybrid = "hybrid" - case standard = "standard" - - var id: String { self.rawValue } - - var description: String { - get { - switch self { - case .satellite: - return NSLocalizedString("satellite", comment: "Satellite Map Type") - case .standard: - return NSLocalizedString("standard", comment: "Standard Map Type") - case .hybrid: - return NSLocalizedString("hybrid", comment: "Hybrid Map Type") - } - } - } -} - -enum LocationUpdateInterval: Int, CaseIterable, Identifiable { - - case fiveSeconds = 5 - case tenSeconds = 10 - case fifteenSeconds = 15 - case thirtySeconds = 30 - case oneMinute = 60 - case fiveMinutes = 300 - case tenMinutes = 600 - case fifteenMinutes = 900 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .fiveSeconds: - return NSLocalizedString("interval.five.seconds", comment: "Five Seconds") - case .tenSeconds: - return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds") - case .fifteenSeconds: - return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") - case .thirtySeconds: - return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") - case .oneMinute: - return NSLocalizedString("interval.one.minute", comment: "One Minute") - case .fiveMinutes: - return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") - case .tenMinutes: - return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") - case .fifteenMinutes: - return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") - } - } - } -} - struct AppSettings: View { @Environment(\.managedObjectContext) var context diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 2f70dfcd..4a6bb4a5 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -276,7 +276,6 @@ struct Channels: View { } } .navigationTitle("channels") - .navigationSplitViewStyle(.automatic) .navigationBarItems(trailing: ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 148b9981..af1cfb79 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -6,66 +6,6 @@ // import SwiftUI -enum UpdateIntervals: Int, CaseIterable, Identifiable { - - case fifteenSeconds = 15 - case thirtySeconds = 30 - case oneMinute = 60 - case fiveMinutes = 300 - case tenMinutes = 600 - case fifteenMinutes = 900 - case thirtyMinutes = 1800 - case oneHour = 3600 - case twoHours = 7200 - case threeHours = 10800 - case fourHours = 14400 - case fiveHours = 18000 - case sixHours = 21600 - case twelveHours = 43200 - case eighteenHours = 64800 - case twentyFourHours = 86400 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .fifteenSeconds: - return "Fifteen Seconds" - case .thirtySeconds: - return "Thirty Seconds" - case .oneMinute: - return "One Minute" - case .fiveMinutes: - return "Five Minutes" - case .tenMinutes: - return "Ten Minutes" - case .fifteenMinutes: - return "Fifteen Minutes" - case .thirtyMinutes: - return "Thirty Minutes" - case .oneHour: - return "One Hour" - case .twoHours: - return "Two Hours" - case .threeHours: - return "Three Hours" - case .fourHours: - return "Four Hours" - case .fiveHours: - return "Five Hours" - case .sixHours: - return "Six Hours" - case .twelveHours: - return "Twelve Hours" - case .eighteenHours: - return "Eighteen Hours" - case .twentyFourHours: - return "Twenty Four Hours" - } - } - } -} - struct TelemetryConfig: View { @Environment(\.managedObjectContext) var context diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 6006109f..1a979dbc 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -113,7 +113,7 @@ struct PositionConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Picker("Position Broadcast Interval", selection: $positionBroadcastSeconds) { - ForEach(PositionBroadcastIntervals.allCases) { at in + ForEach(UpdateIntervals.allCases) { at in Text(at.description) } } diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 06a62a0e..e16f3a06 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -102,9 +102,17 @@ "interval.fifteen.minutes"="Fünfzehn Minutes"; "interval.thirty.minutes"="Dreißig Minutes"; "interval.one.hour"="Eine Stunde"; +"interval.two.hours"="Two Hours"; +"interval.three.hours"="Three Hours"; +"interval.four.hours"="Four Hours"; +"interval.five.hours"="Five Hours"; "interval.six.hours"="Sechs Stunden"; "interval.twelve.hours"="Zwölf Stunden"; +"interval.eighteen.hours"="Eighteen Hours"; "interval.twentyfour.hours"="Vierundzwanzig Stunden"; +"interval.thirtysix.hours"="Thirty Six Hours"; +"interval.fortyeight.hours"="Forty Eight Hours Hours"; +"interval.seventytwo.hours"="Seventy Two Hours"; "keyboard.type"="Keyboard Typ"; "logging"="Logging"; "lora"="LoRa"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index db240f32..b2ac86a9 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -99,9 +99,17 @@ "interval.fifteen.minutes"="Fifteen Minutes"; "interval.thirty.minutes"="Thirty Minutes"; "interval.one.hour"="One Hour"; +"interval.two.hours"="Two Hours"; +"interval.three.hours"="Three Hours"; +"interval.four.hours"="Four Hours"; +"interval.five.hours"="Five Hours"; "interval.six.hours"="Six Hours"; "interval.twelve.hours"="Twelve Hours"; +"interval.eighteen.hours"="Eighteen Hours"; "interval.twentyfour.hours"="Twenty Four Hours"; +"interval.thirtysix.hours"="Thirty Six Hours"; +"interval.fortyeight.hours"="Forty Eight Hours Hours"; +"interval.seventytwo.hours"="Seventy Two Hours"; "keyboard.type"="Keyboard Type"; "logging"="Logging"; "lora"="LoRa"; From 1a1cd719a2490e1d3a24999880192aa2757cf9fc Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 16:45:11 -0800 Subject: [PATCH 12/40] Finish translating enums --- Meshtastic.xcodeproj/project.pbxproj | 8 +- ...ateIntervals.swift => IntervalEnums.swift} | 84 +++++++++++++++++++ .../Module/ExternalNotificationConfig.swift | 43 ---------- .../Config/Module/RangeTestConfig.swift | 38 --------- de.lproj/Localizable.strings | 3 + en.lproj/Localizable.strings | 3 + 6 files changed, 94 insertions(+), 85 deletions(-) rename Meshtastic/Enums/{UpdateIntervals.swift => IntervalEnums.swift} (50%) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 5b5fcca8..d28ae0a6 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -68,7 +68,7 @@ DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DD97E96628EFD9820056DDA4 /* MeshtasticLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */; }; DD97E96828EFE9A00056DDA4 /* About.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD97E96728EFE9A00056DDA4 /* About.swift */; }; - DD994B69295F88B60013760A /* UpdateIntervals.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* UpdateIntervals.swift */; }; + DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD994B68295F88B60013760A /* IntervalEnums.swift */; }; DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA0B6B1294CDC55001356EC /* Channels.swift */; }; DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; }; DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; }; @@ -189,7 +189,7 @@ DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; DD97E96528EFD9820056DDA4 /* MeshtasticLogo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticLogo.swift; sourceTree = ""; }; DD97E96728EFE9A00056DDA4 /* About.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = About.swift; sourceTree = ""; }; - DD994B68295F88B60013760A /* UpdateIntervals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateIntervals.swift; sourceTree = ""; }; + DD994B68295F88B60013760A /* IntervalEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntervalEnums.swift; sourceTree = ""; }; DDA0B6B1294CDC55001356EC /* Channels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channels.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; @@ -376,7 +376,7 @@ DDB6ABE128B13FB500384BA1 /* PositionConfigEnums.swift */, DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */, DD1925B828CDA93900720036 /* SerialConfigEnums.swift */, - DD994B68295F88B60013760A /* UpdateIntervals.swift */, + DD994B68295F88B60013760A /* IntervalEnums.swift */, ); path = Enums; sourceTree = ""; @@ -801,7 +801,7 @@ C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */, DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */, DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, - DD994B69295F88B60013760A /* UpdateIntervals.swift in Sources */, + DD994B69295F88B60013760A /* IntervalEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, diff --git a/Meshtastic/Enums/UpdateIntervals.swift b/Meshtastic/Enums/IntervalEnums.swift similarity index 50% rename from Meshtastic/Enums/UpdateIntervals.swift rename to Meshtastic/Enums/IntervalEnums.swift index 05524eed..5fc0dc20 100644 --- a/Meshtastic/Enums/UpdateIntervals.swift +++ b/Meshtastic/Enums/IntervalEnums.swift @@ -7,6 +7,90 @@ import Foundation +enum OutputIntervals: Int, CaseIterable, Identifiable { + + case unset = 0 + case oneSecond = 1000 + case twoSeconds = 2000 + case threeSeconds = 3000 + case fourSeconds = 4000 + case fiveSeconds = 5000 + case tenSeconds = 10000 + case fifteenSeconds = 15000 + case thirtySeconds = 30000 + case oneMinute = 60000 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + + case .unset: + return NSLocalizedString("unset", comment: "Unset") + case .oneSecond: + return NSLocalizedString("interval.one.second", comment: "One Second") + case .twoSeconds: + return NSLocalizedString("interval.two.seconds", comment: "Two Seconds") + case .threeSeconds: + return NSLocalizedString("interval.three.seconds", comment: "Three Seconds") + case .fourSeconds: + return NSLocalizedString("interval.four.seconds", comment: "Four Seconds") + case .fiveSeconds: + return NSLocalizedString("interval.five.seconds", comment: "Five Seconds") + case .tenSeconds: + return NSLocalizedString("interval.ten.seconds", comment: "Ten Seconds") + case .fifteenSeconds: + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") + case .thirtySeconds: + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") + case .oneMinute: + return NSLocalizedString("interval.one.minute", comment: "One Minute") + } + } + } +} + +// Default of 0 is off +enum SenderIntervals: Int, CaseIterable, Identifiable { + + case off = 0 + case fifteenSeconds = 15 + case thirtySeconds = 30 + case oneMinute = 60 + case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 + case thirtyMinutes = 1800 + case oneHour = 3600 + + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .off: + return NSLocalizedString("off", comment: "Off") + case .fifteenSeconds: + return NSLocalizedString("interval.fifteen.seconds", comment: "Fifteen Seconds") + case .thirtySeconds: + return NSLocalizedString("interval.thirty.seconds", comment: "Thirty Seconds") + case .oneMinute: + return NSLocalizedString("interval.one.minute", comment: "One Minute") + case .fiveMinutes: + return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") + case .tenMinutes: + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") + case .fifteenMinutes: + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") + case .thirtyMinutes: + return NSLocalizedString("interval.thirty.minutes", comment: "Thirty Minutes") + case .oneHour: + return NSLocalizedString("interval.one.hour", comment: "One Hour") + } + } + } +} + enum UpdateIntervals: Int, CaseIterable, Identifiable { case fifteenSeconds = 15 diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index e1bb039e..bcca6a96 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -6,49 +6,6 @@ // import SwiftUI -enum OutputIntervals: Int, CaseIterable, Identifiable { - - case unset = 0 - case oneSecond = 1000 - case twoSeconds = 2000 - case threeSeconds = 3000 - case fourSeconds = 4000 - case fiveSeconds = 5000 - case tenSeconds = 10000 - case fifteenSeconds = 15000 - case thirtySeconds = 30000 - case oneMinute = 60000 - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - - case .unset: - return "Unset" - case .oneSecond: - return "One Second" - case .twoSeconds: - return "Two Seconds" - case .threeSeconds: - return "Three Seconds" - case .fourSeconds: - return "Four Seconds" - case .fiveSeconds: - return "Five Seconds" - case .tenSeconds: - return "Ten Seconds" - case .fifteenSeconds: - return "Fifteen Seconds" - case .thirtySeconds: - return "Thirty Seconds" - case .oneMinute: - return "One Minute" - } - } - } -} - struct ExternalNotificationConfig: View { @Environment(\.managedObjectContext) var context diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index 8e6a6438..e6677593 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -6,44 +6,6 @@ // import SwiftUI -// Default of 0 is off -enum SenderIntervals: Int, CaseIterable, Identifiable { - - case off = 0 - case fifteenSeconds = 15 - case thirtySeconds = 30 - case oneMinute = 60 - case fiveMinutes = 300 - case tenMinutes = 600 - case fifteenMinutes = 900 - case thirtyMinutes = 1800 - - - var id: Int { self.rawValue } - var description: String { - get { - switch self { - case .off: - return "Off" - case .fifteenSeconds: - return "Fifteen Seconds" - case .thirtySeconds: - return "Thirty Seconds" - case .oneMinute: - return "One Minute" - case .fiveMinutes: - return "Five Minutes" - case .tenMinutes: - return "Ten Minutes" - case .fifteenMinutes: - return "Fifteen Minutes" - case .thirtyMinutes: - return "Thirty Minutes" - } - } - } -} - struct RangeTestConfig: View { @Environment(\.managedObjectContext) var context diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index e16f3a06..2cb9f1f9 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -89,6 +89,8 @@ "inputevent.cancel"="Abbrechen"; "interval.one.second"="Eine Sekunde"; "interval.two.seconds"="Zwei Sekunden"; +"interval.three.seconds"="Three Seconds"; +"interval.four.seconds"="Four Seconds"; "interval.five.seconds"="Fünf Sekunden"; "interval.ten.seconds"="Zehn Sekunden"; "interval.fifteen.seconds"="Fünfzehn Sekunden"; @@ -195,6 +197,7 @@ "timeout"="Zeitlimit erreicht"; "twitter"="Twitter"; "unknown.age"="Unbekanntes alter"; +"unset"="Unset"; "update.firmware"="Update Your Firmware"; "update.interval"="Update intervall"; "user"="Benutzer"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index b2ac86a9..060e486f 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -86,6 +86,8 @@ "inputevent.cancel"="Cancel"; "interval.one.second"="One Second"; "interval.two.seconds"="Two Seconds"; +"interval.three.seconds"="Three Seconds"; +"interval.four.seconds"="Four Seconds"; "interval.five.seconds"="Five Seconds"; "interval.ten.seconds"="Ten Seconds"; "interval.fifteen.seconds"="Fifteen Seconds"; @@ -195,6 +197,7 @@ "timeout"="timeout"; "twitter"="Twitter"; "unknown.age"="Unknown Age"; +"unset"="Unset"; "update.firmware"="Update Your Firmware"; "update.interval"="Update Interval"; "user"="User"; From 7c7bcda7c6f3baaa1000e581d38ae81cd119ec67 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 17:04:53 -0800 Subject: [PATCH 13/40] Localize unknown --- Meshtastic/Helpers/BLEManager.swift | 46 +++++++++---------- Meshtastic/Helpers/MeshPackets.swift | 8 ++-- Meshtastic/Views/Bluetooth/Connect.swift | 6 +-- Meshtastic/Views/Map/MapView.swift | 2 +- Meshtastic/Views/Map/MapViewModule.swift | 28 +---------- .../Views/Messages/ChannelMessageList.swift | 2 +- Meshtastic/Views/Messages/Contacts.swift | 2 +- .../Views/Messages/UserMessageList.swift | 2 +- Meshtastic/Views/Nodes/NodeDetail.swift | 2 +- Meshtastic/Views/Nodes/NodeList.swift | 2 +- .../Views/Settings/AdminMessageList.swift | 2 +- de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 1 + 13 files changed, 40 insertions(+), 64 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 9b325cd9..81b65bf1 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -291,7 +291,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return } - MeshLogger.log("ℹ️ Requesting Device Metadata for \(connectedPeripheral!.peripheral.name ?? "Unknown")") + MeshLogger.log("🛎️ Requesting Device Metadata for \(connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") var adminPacket = AdminMessage() adminPacket.getDeviceMetadataRequest = true var meshPacket: MeshPacket = MeshPacket() @@ -348,7 +348,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { invalidVersion = true return } else { - MeshLogger.log("ℹ️ Issuing Want Config to \(connectedPeripheral!.peripheral.name ?? "Unknown")") + MeshLogger.log("ℹ️ Issuing Want Config to \(connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") //BLE Characteristics discovered, issue wantConfig var toRadio: ToRadio = ToRadio() configNonce += 1 @@ -437,9 +437,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if myInfo != nil { connectedPeripheral.num = myInfo!.myNodeNum - connectedPeripheral.firmwareVersion = myInfo?.firmwareVersion ?? "Unknown" - connectedPeripheral.name = myInfo?.bleName ?? "Unknown" - connectedPeripheral.longName = myInfo?.bleName ?? "Unknown" + connectedPeripheral.firmwareVersion = myInfo?.firmwareVersion ?? NSLocalizedString("unknown", comment: "Unknown") + connectedPeripheral.name = myInfo?.bleName ?? NSLocalizedString("unknown", comment: "Unknown") + connectedPeripheral.longName = myInfo?.bleName ?? NSLocalizedString("unknown", comment: "Unknown") } } } @@ -453,7 +453,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo!.num { if nodeInfo!.user != nil { connectedPeripheral.shortName = nodeInfo?.user?.shortName ?? "????" - connectedPeripheral.longName = nodeInfo?.user?.longName ?? "Unknown" + connectedPeripheral.longName = nodeInfo?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") } } } @@ -612,7 +612,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil { connectTo(peripheral: preferredPeripheral!.peripheral) } - MeshLogger.log("🚫 Message Send Failed, not properly connected to \(preferredPeripheral?.name ?? "Unknown")") + MeshLogger.log("🚫 Message Send Failed, not properly connected to \(preferredPeripheral?.name ?? NSLocalizedString("unknown", comment: "Unknown"))") success = false } else if message.count < 1 { @@ -685,7 +685,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let binaryData: Data = try! toRadio.serializedData() - MeshLogger.log("📲 New messageId \(newMessage.messageId) sent to \(newMessage.toUser?.longName! ?? "Unknown")") + MeshLogger.log("📲 New messageId \(newMessage.messageId) sent to \(newMessage.toUser?.longName! ?? NSLocalizedString("unknown", comment: "Unknown"))") if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) do { @@ -991,7 +991,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.decoded = dataMessage - let messageDescription = "Saved Channel \(channel.index) for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Channel \(channel.index) for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1110,7 +1110,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved User Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved User Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) @@ -1133,7 +1133,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Bluetooth Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Bluetooth Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) @@ -1158,7 +1158,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Device Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Device Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1183,7 +1183,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Display Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Display Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) @@ -1207,7 +1207,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved LoRa Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved LoRa Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) @@ -1235,7 +1235,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.decoded = dataMessage - let messageDescription = "Saved Position Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Position Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1264,7 +1264,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.decoded = dataMessage - let messageDescription = "Saved WiFi Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved WiFi Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1292,7 +1292,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.decoded = dataMessage - let messageDescription = "Saved Canned Message Module Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Canned Message Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1321,7 +1321,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.decoded = dataMessage - let messageDescription = "💾 Saved Canned Message Module Messages for \(toUser.longName ?? "Unknown")" + let messageDescription = "💾 Saved Canned Message Module Messages for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1412,7 +1412,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved External Notification Module Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved External Notification Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) } @@ -1438,7 +1438,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.decoded = dataMessage - let messageDescription = "Saved WiFi Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved WiFi Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1466,7 +1466,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.decoded = dataMessage - let messageDescription = "Saved Range Test Module Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Range Test Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { @@ -1494,7 +1494,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Serial Module Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Serial Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) } @@ -1518,7 +1518,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { dataMessage.portnum = PortNum.adminApp meshPacket.decoded = dataMessage - let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "Unknown")" + let messageDescription = "Saved Telemetry Module Config for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { return Int64(meshPacket.id) } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f1464469..092f6622 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1366,12 +1366,12 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM manager.notifications = [ Notification( id: ("notification.id.\(newMessage.messageId)"), - title: "\(newMessage.fromUser?.longName ?? "Unknown")", + title: "\(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))", subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")", content: messageText) ] manager.schedule() - MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown")") + MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") } else if newMessage.fromUser != nil && newMessage.toUser == nil { let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") @@ -1390,12 +1390,12 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM manager.notifications = [ Notification( id: ("notification.id.\(newMessage.messageId)"), - title: "\(newMessage.fromUser?.longName ?? "Unknown")", + title: "\(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))", subtitle: "AKA \(newMessage.fromUser?.shortName ?? "???")", content: messageText) ] manager.schedule() - MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? "Unknown")") + MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") } } } catch { diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index cfa38d5e..87655736 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -39,10 +39,10 @@ struct Connect: View { if node != nil { Text(bleManager.connectedPeripheral.longName).font(.title2) } - Text("ble.name").font(.caption)+Text(": \(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")") + Text("ble.name").font(.caption)+Text(": \(bleManager.connectedPeripheral.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") .font(.caption).foregroundColor(Color.gray) if node != nil { - Text("firmware.version").font(.caption)+Text(": \(node?.myInfo?.firmwareVersion ?? "Unknown")") + Text("firmware.version").font(.caption)+Text(": \(node?.myInfo?.firmwareVersion ?? NSLocalizedString("unknown", comment: "Unknown"))") .font(.caption).foregroundColor(Color.gray) } if bleManager.isSubscribed { @@ -101,7 +101,7 @@ struct Connect: View { Text("Num: \(String(node!.num))") Text("Short Name: \(node?.user?.shortName ?? "????")") - Text("Long Name: \(node?.user?.longName ?? "Unknown")") + Text("Long Name: \(node?.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") Text("Max Channels: \(String(node?.myInfo?.maxChannels ?? 0))") Text("Bitrate: \(String(format: "%.2f", node?.myInfo?.bitrate ?? 0.00))") Text("BLE RSSI: \(bleManager.connectedPeripheral.rssi)") diff --git a/Meshtastic/Views/Map/MapView.swift b/Meshtastic/Views/Map/MapView.swift index 57deab83..02c752be 100644 --- a/Meshtastic/Views/Map/MapView.swift +++ b/Meshtastic/Views/Map/MapView.swift @@ -124,7 +124,7 @@ private extension MapView { if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil { let annotation = PositionAnnotation() annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate! - annotation.title = node.user?.longName ?? "Unknown" + annotation.title = node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") annotation.shortName = node.user?.shortName?.uppercased() ?? "???" view.addAnnotation(annotation) diff --git a/Meshtastic/Views/Map/MapViewModule.swift b/Meshtastic/Views/Map/MapViewModule.swift index fd0b6fe7..13d5bb77 100644 --- a/Meshtastic/Views/Map/MapViewModule.swift +++ b/Meshtastic/Views/Map/MapViewModule.swift @@ -118,20 +118,6 @@ public struct MapView: UIViewRepresentable { mapView.delegate = context.coordinator mapView.register(PositionAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(PositionAnnotationView.self)) - /*Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in - for node in self.locationNodes { - // try and get the last position - if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil { - let annotation = PositionAnnotation() - annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate! - annotation.title = node.user?.longName ?? "Unknown" - annotation.shortName = node.user?.shortName?.uppercased() ?? "???" - - mapView.addAnnotation(annotation) - } - } - }*/ - return mapView } @@ -229,18 +215,6 @@ public struct MapView: UIViewRepresentable { } else { shouldMoveRegion = true } - - /*for node in self.locationNodes { - // try and get the last position - if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil { - let annotation = PositionAnnotation() - annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate! - annotation.title = node.user?.longName ?? "Unknown" - annotation.shortName = node.user?.shortName?.uppercased() ?? "???" - - mapView.addAnnotation(annotation) - } - }*/ var displayedNodes: [Int64] = [] for position in self.positions { @@ -250,7 +224,7 @@ public struct MapView: UIViewRepresentable { let annotation = PositionAnnotation() annotation.coordinate = position.coordinate! - annotation.title = position.nodePosition!.user?.longName ?? "Unknown" + annotation.title = position.nodePosition!.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") annotation.shortName = position.nodePosition!.user?.shortName?.uppercased() ?? "???" mapView.addAnnotation(annotation) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index ba3c638a..732ea084 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -355,7 +355,7 @@ struct ChannelMessageList: View { ToolbarItem(placement: .principal) { HStack { CircleText(text: String(channel.index), color: .accentColor, circleSize: 44, fontSize: 30).fixedSize() - Text(String(channel.name ?? "Unknown").camelCaseToWords()).font(.headline) + Text(String(channel.name ?? NSLocalizedString("unknown", comment: "Unknown")).camelCaseToWords()).font(.headline) } } ToolbarItem(placement: .navigationBarTrailing) { diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index d009d5f6..ec412bd9 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -151,7 +151,7 @@ struct Contacts: View { .padding(.trailing, 5) VStack { HStack { - Text(user.longName ?? "Unknown").font(.headline) + Text(user.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline) Spacer() if user.messageList.count > 0 { VStack (alignment: .trailing) { diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index a9452616..6eb04a9e 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -354,7 +354,7 @@ struct UserMessageList: View { ToolbarItem(placement: .principal) { HStack { CircleText(text: user.shortName ?? "???", color: .accentColor, circleSize: 44, fontSize: 14).fixedSize() - Text(user.longName ?? "Unknown").font(.headline) + Text(user.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline) } } ToolbarItem(placement: .navigationBarTrailing) { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index ee0da7d0..ddeb9161 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -404,7 +404,7 @@ struct NodeDetail: View { .offset( y:-40) } .edgesIgnoringSafeArea([.leading, .trailing]) - .navigationBarTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown", displayMode: .inline) + .navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")), displayMode: .inline) .padding(.bottom, 10) .navigationBarItems(trailing: diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index aaefe178..d8c16f25 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -39,7 +39,7 @@ struct NodeList: View { CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 52, fontSize: 16, brightness: 0.1) .padding(.trailing, 5) VStack(alignment: .leading) { - Text(node.user?.longName ?? "Unknown").font(.headline) + Text(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")).font(.headline) if connected { HStack(alignment: .bottom) { Image(systemName: "repeat.circle.fill") diff --git a/Meshtastic/Views/Settings/AdminMessageList.swift b/Meshtastic/Views/Settings/AdminMessageList.swift index d52e3fd7..3de6324e 100644 --- a/Meshtastic/Views/Settings/AdminMessageList.swift +++ b/Meshtastic/Views/Settings/AdminMessageList.swift @@ -29,7 +29,7 @@ struct AdminMessageList: View { HStack { - Text("\(am.adminDescription ?? "Unknown") - \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)), style: .date) \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)).formattedDate(format: "h:mm:ss a"))") + Text("\(am.adminDescription ?? NSLocalizedString("unknown", comment: "Unknown")) - \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)), style: .date) \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)).formattedDate(format: "h:mm:ss a"))") .font(.caption) if am.receivedACK { diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 2cb9f1f9..cfd71cce 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -196,6 +196,7 @@ "telemetry.config"="Telemetrie Einstellungen"; "timeout"="Zeitlimit erreicht"; "twitter"="Twitter"; +"unknown"="Unknown"; "unknown.age"="Unbekanntes alter"; "unset"="Unset"; "update.firmware"="Update Your Firmware"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 060e486f..a9b6a05a 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -196,6 +196,7 @@ "telemetry.config"="Telemetry Config"; "timeout"="timeout"; "twitter"="Twitter"; +"unknown"="Unknown"; "unknown.age"="Unknown Age"; "unset"="Unset"; "update.firmware"="Update Your Firmware"; From ed9f66148cf461259a3cfa2b48e94ad88e367528 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 17:24:01 -0800 Subject: [PATCH 14/40] Use ack errors for admin message log --- Meshtastic/Views/Messages/ChannelMessageList.swift | 4 ++-- Meshtastic/Views/Messages/UserMessageList.swift | 6 +++--- Meshtastic/Views/Settings/AdminMessageList.swift | 7 ++++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 732ea084..707abee1 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -120,7 +120,7 @@ struct ChannelMessageList: View { Text("waiting") } else if currentUser && message.ackError > 0 { let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) - Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) + Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) } if currentUser { VStack { @@ -184,7 +184,7 @@ struct ChannelMessageList: View { Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange) } else if currentUser && message.ackError > 0 { let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) - Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) + Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) .font(.caption2).foregroundColor(.red) } } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 6eb04a9e..027971cc 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -123,7 +123,7 @@ struct UserMessageList: View { Text("waiting") } else if currentUser && message.ackError > 0 { let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) - Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) + Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) } if currentUser { VStack { @@ -182,12 +182,12 @@ struct UserMessageList: View { let ackErrorVal = RoutingError(rawValue: Int(message.ackError)) if currentUser && message.receivedACK { // Ack Received - Text("\(ackErrorVal?.display ?? "No Error" )").font(.caption2).foregroundColor(message.realACK ? .gray : .orange) + Text("\(ackErrorVal?.display ?? "Empty Ack Error")").font(.caption2).foregroundColor(message.realACK ? .gray : .orange) } else if currentUser && message.ackError == 0 { // Empty Error Text("Waiting to be acknowledged. . .").font(.caption2).foregroundColor(.orange) } else if currentUser && message.ackError > 0 { - Text("\(ackErrorVal?.display ?? "No Error" )").fixedSize(horizontal: false, vertical: true) + Text("\(ackErrorVal?.display ?? "Empty Ack Error")").fixedSize(horizontal: false, vertical: true) .font(.caption2).foregroundColor(.red) } } diff --git a/Meshtastic/Views/Settings/AdminMessageList.swift b/Meshtastic/Views/Settings/AdminMessageList.swift index 3de6324e..28a1d719 100644 --- a/Meshtastic/Views/Settings/AdminMessageList.swift +++ b/Meshtastic/Views/Settings/AdminMessageList.swift @@ -37,15 +37,16 @@ struct AdminMessageList: View { Image(systemName: "checkmark.square") .foregroundColor(.gray) .font(.caption) - Text("Acknowledged: \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))") + Text("acknowledged: \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))") .foregroundColor(.gray) .font(.caption) } else { + let ackErrorVal = RoutingError(rawValue: Int(am.ackError)) Image(systemName: "square") .foregroundColor(.gray) .font(.caption) - Text("Not Acknowledged") + Text(ackErrorVal?.display ?? "Empty Ack Error") .foregroundColor(.gray) .font(.caption) } @@ -53,7 +54,7 @@ struct AdminMessageList: View { } } } - .navigationTitle("Admin Message Log") + .navigationTitle("admin.log") .navigationBarItems(trailing: ZStack { From 77ab77e2c7296a553ce75fb3da4f5bc07acec3f4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 17:44:39 -0800 Subject: [PATCH 15/40] Localize unset --- Meshtastic/Enums/PositionConfigEnums.swift | 6 ++++++ Meshtastic/Enums/SerialConfigEnums.swift | 2 +- Meshtastic/Views/Settings/Config/DeviceConfig.swift | 4 ++-- .../Config/Module/CannedMessagesConfig.swift | 12 +++--------- .../Config/Module/ExternalNotificationConfig.swift | 6 +++--- .../Views/Settings/Config/Module/SerialConfig.swift | 12 ++---------- 6 files changed, 17 insertions(+), 25 deletions(-) diff --git a/Meshtastic/Enums/PositionConfigEnums.swift b/Meshtastic/Enums/PositionConfigEnums.swift index e353e88f..f4d7f4fa 100644 --- a/Meshtastic/Enums/PositionConfigEnums.swift +++ b/Meshtastic/Enums/PositionConfigEnums.swift @@ -132,6 +132,8 @@ enum GpsAttemptTimes: Int, CaseIterable, Identifiable { case oneMinute = 60 case twoMinutes = 120 case fiveMinutes = 300 + case tenMinutes = 600 + case fifteenMinutes = 900 var id: Int { self.rawValue } var description: String { @@ -158,6 +160,10 @@ enum GpsAttemptTimes: Int, CaseIterable, Identifiable { return NSLocalizedString("interval.two.minutes", comment: "Two Minutes") case .fiveMinutes: return NSLocalizedString("interval.five.minutes", comment: "Five Minutes") + case .tenMinutes: + return NSLocalizedString("interval.ten.minutes", comment: "Ten Minutes") + case .fifteenMinutes: + return NSLocalizedString("interval.fifteen.minutes", comment: "Fifteen Minutes") } } } diff --git a/Meshtastic/Enums/SerialConfigEnums.swift b/Meshtastic/Enums/SerialConfigEnums.swift index f6c4dded..38a54de9 100644 --- a/Meshtastic/Enums/SerialConfigEnums.swift +++ b/Meshtastic/Enums/SerialConfigEnums.swift @@ -165,7 +165,7 @@ enum SerialTimeoutIntervals: Int, CaseIterable, Identifiable { get { switch self { case .unset: - return "Unset" + return NSLocalizedString("unset", comment: "Unset") case .oneSecond: return NSLocalizedString("interval.one.second", comment: "One Second") case .fiveSeconds: diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 7a06d1c2..34ae0231 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -63,7 +63,7 @@ struct DeviceConfig: View { Picker("Button GPIO", selection: $buttonGPIO) { ForEach(0..<40) { if $0 == 0 { - Text("Unset") + Text("unset") } else { Text("Pin \($0)") } @@ -73,7 +73,7 @@ struct DeviceConfig: View { Picker("Buzzer GPIO", selection: $buzzerGPIO) { ForEach(0..<40) { if $0 == 0 { - Text("Unset") + Text("unset") } else { Text("Pin \($0)") } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index db08f329..5996f3ff 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -124,9 +124,7 @@ struct CannedMessagesConfig: View { ForEach(0..<40) { if $0 == 0 { - - Text("Unset") - + Text("unset") } else { Text("Pin \($0)") @@ -141,9 +139,7 @@ struct CannedMessagesConfig: View { ForEach(0..<40) { if $0 == 0 { - - Text("Unset") - + Text("unset") } else { Text("Pin \($0)") @@ -158,9 +154,7 @@ struct CannedMessagesConfig: View { ForEach(0..<40) { if $0 == 0 { - - Text("Unset") - + Text("unset") } else { Text("Pin \($0)") diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index bcca6a96..15233435 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -65,7 +65,7 @@ struct ExternalNotificationConfig: View { Picker("Output pin GPIO", selection: $output) { ForEach(0..<40) { if $0 == 0 { - Text("Unset") + Text("unset") } else { Text("Pin \($0)") } @@ -110,7 +110,7 @@ struct ExternalNotificationConfig: View { Picker("Output pin buzzer GPIO ", selection: $outputBuzzer) { ForEach(0..<40) { if $0 == 0 { - Text("Unset") + Text("unset") } else { Text("Pin \($0)") } @@ -120,7 +120,7 @@ struct ExternalNotificationConfig: View { Picker("Output pin vibra GPIO", selection: $outputVibra) { ForEach(0..<40) { if $0 == 0 { - Text("Unset") + Text("unset") } else { Text("Pin \($0)") } diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index d86fca71..a3d50d07 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -74,13 +74,9 @@ struct SerialConfig: View { Picker("Receive data (rxd) GPIO pin", selection: $rxd) { ForEach(0..<40) { - if $0 == 0 { - - Text("Unset") - + Text("unset") } else { - Text("Pin \($0)") } } @@ -89,13 +85,9 @@ struct SerialConfig: View { Picker("Transmit data (txd) GPIO pin", selection: $txd) { ForEach(0..<40) { - if $0 == 0 { - - Text("Unset") - + Text("unset") } else { - Text("Pin \($0)") } } From 99ac6ed6cfa99100ca11bd1c258766faf0f2fb2b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 19:21:22 -0800 Subject: [PATCH 16/40] Remove duplicate mesh logging --- Meshtastic/Helpers/BLEManager.swift | 65 +++----- Meshtastic/Helpers/MeshPackets.swift | 157 ++++-------------- Meshtastic/Views/Nodes/NodeDetail.swift | 4 +- .../Views/Settings/AdminMessageList.swift | 5 +- Meshtastic/Views/Settings/MeshLog.swift | 8 - 5 files changed, 61 insertions(+), 178 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 81b65bf1..7b7074f6 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -348,7 +348,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { invalidVersion = true return } else { - MeshLogger.log("ℹ️ Issuing Want Config to \(connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") + MeshLogger.log("🛎️ Issuing Want Config to \(connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") //BLE Characteristics discovered, issue wantConfig var toRadio: ToRadio = ToRadio() configNonce += 1 @@ -484,16 +484,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } // Log any other unknownApp calls - if !nowKnown { MeshLogger.log("ℹ️ MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") } + if !nowKnown { MeshLogger.log("🌐 MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") } case .textMessageApp: textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) case .remoteHardwareApp: - MeshLogger.log("ℹ️ MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .positionApp: positionPacket(packet: decodedInfo.packet, context: context!) case .waypointApp: - MeshLogger.log("ℹ️ MESH PACKET received for Waypoint App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Waypoint App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .nodeinfoApp: if !invalidVersion { nodeInfoAppPacket(packet: decodedInfo.packet, context: context!) } case .routingApp: @@ -501,29 +501,29 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { case .adminApp: adminAppPacket(packet: decodedInfo.packet, context: context!) case .replyApp: - MeshLogger.log("ℹ️ MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .ipTunnelApp: - MeshLogger.log("ℹ️ MESH PACKET received for IP Tunnel App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for IP Tunnel App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .serialApp: - MeshLogger.log("ℹ️ MESH PACKET received for Serial App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Serial App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .storeForwardApp: - MeshLogger.log("ℹ️ MESH PACKET received for Store Forward App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Store Forward App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .rangeTestApp: - MeshLogger.log("ℹ️ MESH PACKET received for Range Test App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Range Test App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .telemetryApp: if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } case .textMessageCompressedApp: - MeshLogger.log("ℹ️ MESH PACKET received for Text Message Compressed App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Text Message Compressed App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .zpsApp: - MeshLogger.log("ℹ️ MESH PACKET received for ZPS App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for ZPS App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .privateApp: - MeshLogger.log("ℹ️ MESH PACKET received for Private App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Private App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .atakForwarder: - MeshLogger.log("ℹ️ MESH PACKET received for ATAK Forwarder App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for ATAK Forwarder App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .simulatorApp: - MeshLogger.log("ℹ️ MESH PACKET received for Simulator App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Simulator App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .audioApp: - MeshLogger.log("ℹ️ MESH PACKET received for Audio App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Audio App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .tracerouteApp: if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { @@ -540,7 +540,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } case .UNRECOGNIZED(_): - MeshLogger.log("ℹ️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🌐 MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .max: print("MAX PORT NUM OF 511") } @@ -809,43 +809,26 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } - public func sendShutdown(destNum: Int64) -> Bool { + public func sendShutdown(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.shutdownSeconds = 10 - var meshPacket: MeshPacket = MeshPacket() - meshPacket.to = UInt32(connectedPeripheral.num) - meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. MyInfoEntity? { + MeshLogger.log("ℹ️ MyInfo received: \(String(myInfo.myNodeNum))") let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum)) @@ -732,18 +685,15 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO myInfoEntity.messageTimeoutMsec = Int32(bitPattern: myInfo.messageTimeoutMsec) myInfoEntity.minAppVersion = Int32(bitPattern: myInfo.minAppVersion) myInfoEntity.maxChannels = Int32(bitPattern: myInfo.maxChannels) - do { try context.save() - MeshLogger.log("💾 Saved a new myInfo for node number: \(String(myInfo.myNodeNum))") + print("💾 Saved a new myInfo for node number: \(String(myInfo.myNodeNum))") return myInfoEntity - } catch { context.rollback() let nsError = error as NSError print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)") } - } else { fetchedMyInfo[0].peripheralId = peripheralId @@ -760,9 +710,8 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO do { try context.save() - MeshLogger.log("💾 Updated myInfo for node number: \(String(myInfo.myNodeNum))") + print("💾 Updated myInfo for node number: \(String(myInfo.myNodeNum))") return fetchedMyInfo[0] - } catch { context.rollback() let nsError = error as NSError @@ -770,7 +719,6 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO } } } catch { - print("💥 Fetch MyInfo Error") } return nil @@ -780,15 +728,14 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo if channel.isInitialized && channel.hasSettings && channel.role != Channel.Role.disabled { + MeshLogger.log("🎛️ Channel received: \(channel.settings.name)") let fetchedMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum) do { let fetchedMyInfo = try context.fetch(fetchedMyInfoRequest) as! [MyInfoEntity] - if fetchedMyInfo.count == 1 { - let newChannel = ChannelEntity(context: context) newChannel.id = Int32(channel.index) newChannel.index = Int32(channel.index) @@ -809,15 +756,12 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo } catch { print("Failed to save channel") } - MeshLogger.log("💾 Updated MyInfo channel \(channel.index) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)") + print("💾 Updated MyInfo channel \(channel.index) from Channel App Packet For: \(fetchedMyInfo[0].myNodeNum)") } else if channel.role.rawValue > 0 { print("💥 Trying to save a channel to a MyInfo that does not exist: \(fromNum)") } - } catch { - context.rollback() - let nsError = error as NSError print("💥 Error Saving MyInfo Channel from ADMIN_APP \(nsError)") } @@ -841,14 +785,11 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newNode.channel = Int32(channel) if nodeInfo.hasDeviceMetrics { - let telemetry = TelemetryEntity(context: context) - telemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) telemetry.voltage = nodeInfo.deviceMetrics.voltage telemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization telemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx - var newTelemetries = [TelemetryEntity]() newTelemetries.append(telemetry) newNode.telemetries? = NSOrderedSet(array: newTelemetries) @@ -856,9 +797,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) newNode.snr = nodeInfo.snr - if nodeInfo.hasUser { - let newUser = UserEntity(context: context) newUser.userId = nodeInfo.user.id newUser.num = Int64(nodeInfo.num) @@ -870,7 +809,6 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } if nodeInfo.position.latitudeI > 0 || nodeInfo.position.longitudeI > 0 { - let position = PositionEntity(context: context) position.seqNo = Int32(nodeInfo.position.seqNumber) position.latitudeI = nodeInfo.position.latitudeI @@ -880,7 +818,6 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje position.speed = Int32(nodeInfo.position.groundSpeed) position.heading = Int32(nodeInfo.position.groundTrack) position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) - var newPostions = [PositionEntity]() newPostions.append(position) newNode.positions? = NSOrderedSet(array: newPostions) @@ -896,30 +833,22 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje if fetchedMyInfo.count > 0 { newNode.myInfo = fetchedMyInfo[0] } - do { - try context.save() - if nodeInfo.hasUser { - MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.user.longName)") + MeshLogger.log("📟 Node info received for: \(nodeInfo.user.longName)") } else { - MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.num)") + MeshLogger.log("📟 Node info received for: \(nodeInfo.num)") } return newNode - } catch { - context.rollback() - let nsError = error as NSError print("💥 Error Saving Core Data NodeInfoEntity: \(nsError)") } - } catch { print("💥 Fetch MyInfo Error") } - } else if nodeInfo.hasUser && nodeInfo.num > 0 { fetchedNode[0].id = Int64(nodeInfo.num) @@ -960,11 +889,8 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje position.altitude = nodeInfo.position.altitude position.satsInView = Int32(nodeInfo.position.satsInView) position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) - let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet - fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet - } // Look for a MyInfo @@ -972,44 +898,31 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num)) do { - let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity] if fetchedMyInfo.count > 0 { - fetchedNode[0].myInfo = fetchedMyInfo[0] } - do { - try context.save() if nodeInfo.hasUser { - MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.user.longName)") - + MeshLogger.log("📟 Node info received for: \(nodeInfo.user.longName)") } else { - MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.num)") + MeshLogger.log("📟 Node info received for: \(nodeInfo.num)") } - return fetchedNode[0] - } catch { - context.rollback() - let nsError = error as NSError print("💥 Error Saving Core Data NodeInfoEntity: \(nsError)") } - } catch { print("💥 Fetch MyInfo Error") } } - } catch { - print("💥 Fetch NodeInfoEntity Error") } - return nil } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index ddeb9161..20bf8532 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -362,9 +362,7 @@ struct NodeDetail: View { isPresented: $showingShutdownConfirm ) { Button("Shutdown Node?", role: .destructive) { - - if !bleManager.sendShutdown(destNum: node.num) { - + if !bleManager.sendShutdown(fromUser: node.user!, toUser: node.user!) { print("Shutdown Failed") } } diff --git a/Meshtastic/Views/Settings/AdminMessageList.swift b/Meshtastic/Views/Settings/AdminMessageList.swift index 28a1d719..ac98d635 100644 --- a/Meshtastic/Views/Settings/AdminMessageList.swift +++ b/Meshtastic/Views/Settings/AdminMessageList.swift @@ -37,7 +37,7 @@ struct AdminMessageList: View { Image(systemName: "checkmark.square") .foregroundColor(.gray) .font(.caption) - Text("acknowledged: \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))") + Text("routing.acknowledged").foregroundColor(.gray).font(.caption) + Text(": \(Date(timeIntervalSince1970: TimeInterval(am.ackTimestamp)).formattedDate(format: "h:mm:ss a"))") .foregroundColor(.gray) .font(.caption) @@ -56,13 +56,10 @@ struct AdminMessageList: View { } .navigationTitle("admin.log") .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) .onAppear { - self.bleManager.context = context } } diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index 1463883f..da9eba50 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -14,36 +14,28 @@ struct MeshLog: View { List(logs, id: \.self, rowContent: Text.init) .task { do { - let url = logFile! logs.removeAll() - var lineCount = 0 let lineLimit = 500 - // Get the number of lines for try await _ in url.lines { lineCount += 1 } - // Set the record to start with if we have more lines than the limit var startingLog = 0 if lineCount > lineLimit { startingLog = lineCount - lineLimit } - var lineNumber = 0 - for try await log in url.lines { if lineNumber >= startingLog { - logs.append(log) document.logFile.append("\(log) \n") } lineNumber += 1 } logs.reverse() - } catch { // Stop adding logs when an error is thrown } From 804345d1f1b72125b4fb296d0675228b36d21e11 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 20:04:43 -0800 Subject: [PATCH 17/40] More mesh log cleanup --- Meshtastic/Helpers/BLEManager.swift | 14 +++--- Meshtastic/Helpers/MeshPackets.swift | 66 +++++++++---------------- Meshtastic/Views/Settings/MeshLog.swift | 17 ++----- 3 files changed, 32 insertions(+), 65 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 7b7074f6..ca8fbd25 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -695,11 +695,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { success = true } catch { - context!.rollback() - let nsError = error as NSError - MeshLogger.log("💥 Unresolved Core Data error in Send Message Function your database is corrupted running a node db reset should clean up the data. Error: \(nsError)") + print("💥 Unresolved Core Data error in Send Message Function your database is corrupted running a node db reset should clean up the data. Error: \(nsError)") } } } @@ -866,7 +864,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } catch { context!.rollback() let nsError = error as NSError - MeshLogger.log("💥 Error Inserting New Core Data MessageEntity: \(nsError)") + print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") } } return false @@ -1043,7 +1041,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse) - MeshLogger.log("✈️ Sent a Channel for: \(String(self.connectedPeripheral.num)) Channel Index \(chan.index)") + MeshLogger.log("🎛️ Sent a Channel for: \(String(self.connectedPeripheral.num)) Channel Index \(chan.index)") } } // Save the LoRa Config and the device will reboot @@ -1066,7 +1064,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse) - MeshLogger.log("✈️ Sent a LoRaConfig for: \(String(self.connectedPeripheral.num))") + MeshLogger.log("📻 Sent a LoRaConfig for: \(String(self.connectedPeripheral.num))") } return true @@ -1370,7 +1368,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { - MeshLogger.log("✈️ Sent a Canned Messages Module Get Messages Request Admin Message for node: \(String(destNum))") + MeshLogger.log("🥫 Requested Canned Messages Module Messages for node: \(String(destNum))") connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true } @@ -1534,7 +1532,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } catch { context!.rollback() let nsError = error as NSError - MeshLogger.log("💥 Error inserting new core data MessageEntity: \(nsError)") + print("💥 Error inserting new core data MessageEntity: \(nsError)") } } return false diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 2ee650df..8161c038 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -373,19 +373,17 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum do { try context.save() print("💾 Updated Canned Message Module Config for node number: \(String(nodeNum))") - } catch { context.rollback() let nsError = error as NSError - MeshLogger.log("💥 Error Updating Core Data CannedMessageConfigEntity: \(nsError)") + print("💥 Error Updating Core Data CannedMessageConfigEntity: \(nsError)") } } else { - MeshLogger.log("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Canned Message Module Config") + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save Canned Message Module Config") } - } catch { let nsError = error as NSError - MeshLogger.log("💥 Fetching node for core data CannedMessageConfigEntity failed: \(nsError)") + print("💥 Fetching node for core data CannedMessageConfigEntity failed: \(nsError)") } } @@ -440,19 +438,15 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum do { try context.save() print("💾 Updated External Notification Module Config for node number: \(String(nodeNum))") - } catch { context.rollback() let nsError = error as NSError - MeshLogger.log("💥 Error Updating Core Data ExternalNotificationConfigEntity: \(nsError)") + print("💥 Error Updating Core Data ExternalNotificationConfigEntity: \(nsError)") } - } else { - MeshLogger.log("💥 No Nodes found in local database matching node number \(nodeNum) unable to save External Notifiation Module Config") + print("💥 No Nodes found in local database matching node number \(nodeNum) unable to save External Notifiation Module Config") } - } catch { - let nsError = error as NSError print("💥 Fetching node for core data ExternalNotificationConfigEntity failed: \(nsError)") } @@ -770,6 +764,12 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? { + if nodeInfo.hasUser { + MeshLogger.log("📟 Node info received for: \(nodeInfo.user.longName)") + } else { + MeshLogger.log("📟 Node info received for: \(nodeInfo.num)") + } + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num)) @@ -835,11 +835,6 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } do { try context.save() - if nodeInfo.hasUser { - MeshLogger.log("📟 Node info received for: \(nodeInfo.user.longName)") - } else { - MeshLogger.log("📟 Node info received for: \(nodeInfo.num)") - } return newNode } catch { context.rollback() @@ -871,12 +866,10 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje if nodeInfo.hasDeviceMetrics { let newTelemetry = TelemetryEntity(context: context) - newTelemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel) newTelemetry.voltage = nodeInfo.deviceMetrics.voltage newTelemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization newTelemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx - let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as! NSMutableOrderedSet fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet } @@ -926,9 +919,10 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje return nil } - func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { + MeshLogger.log("📟 Node info received for: \(packet.from)") + let fetchNodeInfoAppRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) @@ -944,22 +938,17 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { fetchedNode[0].channel = Int32(packet.channel) if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) { - if nodeInfoMessage.hasDeviceMetrics { - let telemetry = TelemetryEntity(context: context) - telemetry.batteryLevel = Int32(nodeInfoMessage.deviceMetrics.batteryLevel) telemetry.voltage = nodeInfoMessage.deviceMetrics.voltage telemetry.channelUtilization = nodeInfoMessage.deviceMetrics.channelUtilization telemetry.airUtilTx = nodeInfoMessage.deviceMetrics.airUtilTx - var newTelemetries = [TelemetryEntity]() newTelemetries.append(telemetry) fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries) } if nodeInfoMessage.hasUser { - fetchedNode[0].user!.userId = nodeInfoMessage.user.id fetchedNode[0].user!.num = Int64(nodeInfoMessage.num) fetchedNode[0].user!.longName = nodeInfoMessage.user.longName @@ -968,36 +957,33 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { fetchedNode[0].user!.hwModel = String(describing: nodeInfoMessage.user.hwModel).uppercased() } } - do { try context.save() - MeshLogger.log("💾 Updated NodeInfo from Node Info App Packet For: \(fetchedNode[0].num)") + print("💾 Updated NodeInfo from Node Info App Packet For: \(fetchedNode[0].num)") } catch { context.rollback() let nsError = error as NSError - MeshLogger.log("💥 Error Saving NodeInfoEntity from NODEINFO_APP \(nsError)") + print("💥 Error Saving NodeInfoEntity from NODEINFO_APP \(nsError)") } - } else { - // New node info not from device but potentially from another network } - } catch { - MeshLogger.log("💥 Error Fetching NodeInfoEntity for NODEINFO_APP") + print("💥 Error Fetching NodeInfoEntity for NODEINFO_APP") } } func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { + if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { + MeshLogger.log("🥫 Canned Messages Messages Received For: \(packet.from)") let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) do { let fetchedNode = try context.fetch(fetchNodeRequest) as! [NodeInfoEntity] if fetchedNode.count == 1 { - let messages = String(cmmc.textFormatString()) .replacingOccurrences(of: "11: ", with: "") .replacingOccurrences(of: "\"", with: "") @@ -1005,16 +991,13 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { fetchedNode[0].cannedMessageConfig?.messages = messages do { try context.save() - MeshLogger.log("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") + print("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") } catch { - context.rollback() - let nsError = error as NSError print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") } } - } catch { print("💥 Error Deserializing POSITION_APP packet.") } @@ -1210,21 +1193,18 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage fetchedNode[0].lastHeard = telemetry.time fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet } - try context.save() // Only log telemetery from the mesh not the connected device if connectedNode != Int64(packet.from) { MeshLogger.log("💾 Telemetry Saved for Node: \(packet.from)") } - } catch { context.rollback() let nsError = error as NSError - MeshLogger.log("💥 Error Saving Telemetry for Node \(packet.from) Error: \(nsError)") + print("💥 Error Saving Telemetry for Node \(packet.from) Error: \(nsError)") } - } else { - MeshLogger.log("💥 Error Fetching NodeInfoEntity for Node \(packet.from)") + print("💥 Error Fetching NodeInfoEntity for Node \(packet.from)") } } @@ -1320,10 +1300,10 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM } catch { context.rollback() let nsError = error as NSError - MeshLogger.log("💥 Failed to save new MessageEntity \(nsError)") + print("💥 Failed to save new MessageEntity \(nsError)") } } catch { - MeshLogger.log("💥 Fetch Message To and From Users Error") + print("💥 Fetch Message To and From Users Error") } } } diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index da9eba50..63f86766 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -46,7 +46,6 @@ struct MeshLog: View { contentType: UTType.plainText, defaultFilename: "mesh-activity-log", onCompletion: { result in - if case .success = result { print("Mesh activity log download: success.") } else { @@ -55,13 +54,11 @@ struct MeshLog: View { } ) .textSelection(.enabled) - .font(.caption2) - + .font(.caption) + HStack(alignment: .center) { Spacer() - Button(role: .destructive) { - let text = "" do { try text.write(to: logFile!, atomically: false, encoding: .utf8) @@ -69,35 +66,27 @@ struct MeshLog: View { } catch { print(error) } - } label: { - Label("Clear Log", systemImage: "trash.fill") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) .padding() - Spacer() Button { - isExporting = true - } label: { - Label("Save Log", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) .padding() - Spacer() - } .padding(.bottom, 10) - .navigationTitle("Mesh Activity Log") + .navigationTitle("mesh.log") } } From ae2f860c21afa5fb01e45c8285d0d9f0a8692517 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 21:28:51 -0800 Subject: [PATCH 18/40] Clean up the mesh logging some more --- Meshtastic/Helpers/BLEManager.swift | 3 +- Meshtastic/Helpers/MeshPackets.swift | 64 +++++++++------------------- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index ca8fbd25..99f3e0b6 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -688,10 +688,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { MeshLogger.log("📲 New messageId \(newMessage.messageId) sent to \(newMessage.toUser?.longName! ?? NSLocalizedString("unknown", comment: "Unknown"))") if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) + MeshLogger.log("💬 Sent a message from \(connectedPeripheral.num) to \(toUserNum)") do { try context!.save() - MeshLogger.log("💾 Saved a new sent message from \(connectedPeripheral.num) to \(toUserNum)") + print("💾 Saved a new sent message from \(connectedPeripheral.num) to \(toUserNum)") success = true } catch { diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 8161c038..0b324843 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1004,9 +1004,10 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } } - func positionPacket (packet: MeshPacket, context: NSManagedObjectContext) { + MeshLogger.log("📍 Position Packet received from node: \(packet.from)") + let fetchNodePositionRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) @@ -1045,7 +1046,7 @@ func positionPacket (packet: MeshPacket, context: NSManagedObjectContext) { do { try context.save() - MeshLogger.log("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") + print("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") } catch { context.rollback() @@ -1068,38 +1069,9 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana if let routingMessage = try? Routing(serializedData: packet.decoded.payload) { - let error = routingMessage.errorReason - - var errorExplanation = "Unknown Routing Error" - - switch error { - case Routing.Error.none: - errorExplanation = "This message is not a failure" - case Routing.Error.noRoute: - errorExplanation = "Our node doesn't have a route to the requested destination anymore." - case Routing.Error.gotNak: - errorExplanation = "We received a nak while trying to forward on your behalf" - case Routing.Error.timeout: - errorExplanation = "Timeout" - case Routing.Error.noInterface: - errorExplanation = "No suitable interface could be found for delivering this packet" - case Routing.Error.maxRetransmit: - errorExplanation = "We reached the max retransmission count (typically for naive flood routing)" - case Routing.Error.noChannel: - errorExplanation = "No suitable channel was found for sending this packet (i.e. was requested channel index disabled?)" - case Routing.Error.tooLarge: - errorExplanation = "The packet was too big for sending (exceeds interface MTU after encoding)" - case Routing.Error.noResponse: - errorExplanation = "The request had want_response set, the request reached the destination node, but no service on that node wants to send a response (possibly due to bad channel permissions)" - case Routing.Error.badRequest: - errorExplanation = "The application layer service on the remote node received your request, but considered your request somehow invalid" - case Routing.Error.notAuthorized: - errorExplanation = "The application layer service on the remote node received your request, but considered your request not authorized (i.e you did not send the request on the required bound channel)" - fallthrough - default: () - } - - MeshLogger.log("🕸️ ROUTING PACKET received for RequestID: \(packet.decoded.requestID) Error: \(errorExplanation)") + let routingError = RoutingError(rawValue: routingMessage.errorReason.rawValue) + + MeshLogger.log("🕸️ Routing received for RequestID: \(packet.decoded.requestID) Ack Status: \(routingError?.display ?? NSLocalizedString("unknown", comment: ""))") let fetchMessageRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MessageEntity") fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID)) @@ -1147,11 +1119,11 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana return } try context.save() - MeshLogger.log("💾 ACK Received and saved for MessageID \(packet.decoded.requestID)") + print("💾 ACK Saved for Message: \(packet.decoded.requestID)") } catch { context.rollback() let nsError = error as NSError - MeshLogger.log("💥 Error Saving ACK for message MessageID \(packet.id) Error: \(nsError)") + print("💥 Error Saving ACK for message: \(packet.id) Error: \(nsError)") } } } @@ -1160,7 +1132,12 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) { - let telemetry = TelemetryEntity(context: context) + // Only log telemetry from the mesh not the connected device + if connectedNode != Int64(packet.from) { + MeshLogger.log("📈 Telemetry received for: \(String(packet.from))") + } + + let telemetry = TelemetryEntity(context: context) let fetchNodeTelemetryRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) @@ -1194,9 +1171,9 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet } try context.save() - // Only log telemetery from the mesh not the connected device + // Only log telemetry from the mesh not the connected device if connectedNode != Int64(packet.from) { - MeshLogger.log("💾 Telemetry Saved for Node: \(packet.from)") + print("💾 Telemetry Saved for Node: \(packet.from)") } } catch { context.rollback() @@ -1212,7 +1189,7 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM if let messageText = String(bytes: packet.decoded.payload, encoding: .utf8) { - MeshLogger.log("💬 Message received for text message app") + MeshLogger.log("💬 Message received from the text message app") let messageUsers: NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from]) @@ -1248,7 +1225,7 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM do { try context.save() - MeshLogger.log("💾 Saved a new message for \(newMessage.messageId)") + print("💾 Saved a new message for \(newMessage.messageId)") messageSaved = true if messageSaved { @@ -1264,7 +1241,7 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM content: messageText) ] manager.schedule() - MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") + print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") } else if newMessage.fromUser != nil && newMessage.toUser == nil { let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") @@ -1288,12 +1265,11 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, context: NSM content: messageText) ] manager.schedule() - MeshLogger.log("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") + print("💬 iOS Notification Scheduled for text message from \(newMessage.fromUser?.longName ?? NSLocalizedString("unknown", comment: "Unknown"))") } } } catch { - } } } From 82d7f5555261c53b213e7e256d8b2c99485c3018 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Dec 2022 22:30:15 -0800 Subject: [PATCH 19/40] Use iPhone 24 hour setting for date formatting Clean up several inconsistent admin message methods --- Meshtastic/Export/WriteCsvFile.swift | 6 +- Meshtastic/Helpers/BLEManager.swift | 97 +++++++------------ Meshtastic/Helpers/MeshPackets.swift | 14 +-- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 4 +- .../Views/Nodes/EnvironmentMetricsLog.swift | 4 +- Meshtastic/Views/Nodes/NodeDetail.swift | 48 +++------ Meshtastic/Views/Nodes/PositionLog.swift | 6 +- .../Views/Settings/Config/DeviceConfig.swift | 8 +- .../Config/Module/CannedMessagesConfig.swift | 2 +- de.lproj/Localizable.strings | 2 + en.lproj/Localizable.strings | 2 + 11 files changed, 66 insertions(+), 127 deletions(-) diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index b0de58cc..c7003bae 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -23,7 +23,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin csvString += ", " csvString += String(dm.airUtilTx) csvString += ", " - csvString += dm.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") + csvString += dm.time?.formattedDate(format: "yyyy-MM-dd j:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") } } } else if metricsType == 1 { @@ -44,7 +44,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin csvString += ", " csvString += String(dm.current) csvString += ", " - csvString += dm.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") + csvString += dm.time?.formattedDate(format: "yyyy-MM-dd j:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") } } } @@ -73,7 +73,7 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { csvString += ", " csvString += String(pos.snr) csvString += ", " - csvString += pos.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") + csvString += pos.time?.formattedDate(format: "yyyy-MM-dd j:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") } return csvString } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 99f3e0b6..cbe4fbd5 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -831,14 +831,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { return false } - public func sendReboot(destNum: Int64) -> Bool { + public func sendReboot(fromUser: UserEntity, toUser: UserEntity) -> Bool { var adminPacket = AdminMessage() adminPacket.rebootSeconds = 10 var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(connectedPeripheral.num) - meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { - - var adminPacket = AdminMessage() - adminPacket.factoryReset = 1 - - var meshPacket: MeshPacket = MeshPacket() - meshPacket.to = UInt32(destNum) - meshPacket.from = 0 //UInt32(connectedPeripheral.num) - meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + public func sendFactoryReset(fromUser: UserEntity, toUser: UserEntity) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.factoryReset = 1 + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() adminPacket.nodedbReset = 1 var meshPacket: MeshPacket = MeshPacket() - meshPacket.to = UInt32(destNum) - meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.to = UInt32(toUser.num) + meshPacket.from = UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. NodeInfoEntity? { - if nodeInfo.hasUser { - MeshLogger.log("📟 Node info received for: \(nodeInfo.user.longName)") - } else { - MeshLogger.log("📟 Node info received for: \(nodeInfo.num)") - } + MeshLogger.log("📟 Node info received for: \(nodeInfo.num)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num)) @@ -897,12 +893,8 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje } do { try context.save() - - if nodeInfo.hasUser { - MeshLogger.log("📟 Node info received for: \(nodeInfo.user.longName)") - } else { - MeshLogger.log("📟 Node info received for: \(nodeInfo.num)") - } + print("💾 NodeInfo saved for \(nodeInfo.num)") + return fetchedNode[0] } catch { context.rollback() diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 903d94b3..59b9dcc6 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -68,7 +68,7 @@ struct DeviceMetricsLog: View { } TableColumn("Time Stamp") { dm in if dm.metricsType == 0 { - Text(dm.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + Text(dm.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") } } } @@ -117,7 +117,7 @@ struct DeviceMetricsLog: View { .font(.caption) Text("\(String(format: "%.2f", dm.airUtilTx))%") .font(.caption) - Text(dm.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + Text(dm.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") .font(.caption) } } diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 18f72249..1f4a3b24 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -57,7 +57,7 @@ struct EnvironmentMetricsLog: View { } TableColumn("Time Stamp") { em in if em.metricsType == 1 { - Text(em.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + Text(em.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") } } } @@ -104,7 +104,7 @@ struct EnvironmentMetricsLog: View { .font(.caption) Text("\(String(format: "%.2f", em.gasResistance))") .font(.caption) - Text(em.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + Text(em.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") .font(.caption) } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 20bf8532..e41cae3e 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -192,28 +192,21 @@ struct NodeDetail: View { HStack { VStack(alignment: .center) { - CircleText(text: node.user?.shortName ?? "???", color: .accentColor) } - Divider() - VStack { - if node.user != nil { - Image(node.user!.hwModel ?? "UNSET") .resizable() .frame(width: 75, height: 75) .cornerRadius(5) - Text(String(node.user!.hwModel ?? "UNSET")) .font(.callout).fixedSize() } } .padding(5) - if node.snr > 0 { Divider() VStack(alignment: .center) { @@ -375,26 +368,23 @@ struct NodeDetail: View { }) { - Label("Reboot", systemImage: "arrow.triangle.2.circlepath") + Label("reboot", systemImage: "arrow.triangle.2.circlepath") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) .padding() - .confirmationDialog( - - "are.you.sure", - isPresented: $showingRebootConfirm - ) { - - Button("Reboot Node?", role: .destructive) { - - if !bleManager.sendReboot(destNum: node.num) { - - print("Reboot Failed") + .confirmationDialog("are.you.sure", + + isPresented: $showingRebootConfirm + ) { + Button("reboot.node", role: .destructive) { + + if !bleManager.sendReboot(fromUser: node.user!, toUser: node.user!) { + print("Reboot Failed") + } + } } - } - } } .padding(5) } @@ -402,12 +392,10 @@ struct NodeDetail: View { .offset( y:-40) } .edgesIgnoringSafeArea([.leading, .trailing]) - .navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown")), displayMode: .inline) + .navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline) .padding(.bottom, 10) .navigationBarItems(trailing: - ZStack { - ConnectedDevice( bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, @@ -421,15 +409,3 @@ struct NodeDetail: View { } } } - -struct NodeInfoEntityDetail_Previews: PreviewProvider { - - static let bleManager = BLEManager() - - static var previews: some View { - Group { - - // NodeDetail(node: node) - } - } -} diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 4554c7b9..8fe6e582 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -50,7 +50,7 @@ struct PositionLog: View { Text("\(String(format: "%.2f", position.snr)) dB") } TableColumn("Time Stamp") { position in - Text(position.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + Text(position.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") } } @@ -95,7 +95,7 @@ struct PositionLog: View { .font(.caption2) Text(String(mappin.altitude)) .font(.caption2) - Text(mappin.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + Text(mappin.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") .font(.caption2) } } @@ -125,9 +125,7 @@ struct PositionLog: View { titleVisibility: .visible ) { Button("Delete all positions?", role: .destructive) { - if clearPositions(destNum: node.num, context: context) { - print("Successfully Cleared Position Log") } else { diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 34ae0231..0fb1e14c 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -90,7 +90,7 @@ struct DeviceConfig: View { Button("Reset NodeDB", role: .destructive) { isPresentingNodeDBResetConfirm = true } - .disabled(bleManager.connectedPeripheral == nil) + .disabled(node?.user == nil) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) @@ -101,7 +101,7 @@ struct DeviceConfig: View { titleVisibility: .visible ) { Button("Erase all device and app data?", role: .destructive) { - if bleManager.sendNodeDBReset(destNum: bleManager.connectedPeripheral.num) { + if bleManager.sendNodeDBReset(fromUser: node!.user!, toUser: node!.user!) { bleManager.disconnectPeripheral() clearCoreDataDatabase(context: context) } else { @@ -112,7 +112,7 @@ struct DeviceConfig: View { Button("Factory Reset", role: .destructive) { isPresentingFactoryResetConfirm = true } - .disabled(bleManager.connectedPeripheral == nil) + .disabled(node?.user == nil) .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(.large) @@ -124,7 +124,7 @@ struct DeviceConfig: View { ) { Button("Factory reset your device and app? ", role: .destructive) { - if bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num) { + if bleManager.sendFactoryReset(fromUser: node!.user!, toUser: node!.user!) { bleManager.disconnectPeripheral() clearCoreDataDatabase(context: context) } else { diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 5996f3ff..01d51a62 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -256,7 +256,7 @@ struct CannedMessagesConfig: View { // for now just disable the button after a successful save hasMessagesChanges = false if !hasChanges { - bleManager.sendReboot(destNum: node!.num) + bleManager.sendReboot(fromUser: node!.user!, toUser: node!.user!) goBack() } } diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index cfd71cce..ddd6abbf 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -150,6 +150,8 @@ "radio.configuration"="Geräteeinstellungen"; "range.test"="Entfernungstest"; "range.test.config"="Entfernungstest Konfiguration"; +"reboot"="Reboot"; +"reboot.node"="Reboot node?"; "reply"="Antworten"; "received.ack"="Empfangsbestätigung"; "received.ack.real"="Recipient Ack"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index a9b6a05a..fa2ca147 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -151,6 +151,8 @@ "range.test"="Range Test"; "range.test.config"="Range Test Config"; "reply"="Reply"; +"reboot"="Reboot"; +"reboot.node"="Reboot node?"; "received.ack"="Received Ack"; "received.ack.real"="Recipient Ack"; "routing.acknowledged"="Acknowledged"; From 6ee12efdb878b4b8db53cbea3633a4ef14c09245 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 31 Dec 2022 00:26:59 -0800 Subject: [PATCH 20/40] Localized dates --- Meshtastic/Export/WriteCsvFile.swift | 10 +++++++--- Meshtastic/Helpers/MeshLogger.swift | 6 ++++-- .../Views/Messages/ChannelMessageList.swift | 9 +++++---- Meshtastic/Views/Messages/Contacts.swift | 10 ++++++---- Meshtastic/Views/Messages/UserMessageList.swift | 7 +++++-- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 17 ++++++++++------- .../Views/Nodes/EnvironmentMetricsLog.swift | 15 ++++++++------- Meshtastic/Views/Nodes/PositionLog.swift | 6 ++++-- .../Views/Settings/AdminMessageList.swift | 5 +++-- 9 files changed, 52 insertions(+), 33 deletions(-) diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index c7003bae..0dbc4d69 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -9,6 +9,8 @@ import SwiftUI func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> String { var csvString: String = "" + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma") if metricsType == 0 { // Create Device Metrics Header csvString = "Battery Level, Voltage, Channel Utilization, Airtime, Timestamp" @@ -23,7 +25,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin csvString += ", " csvString += String(dm.airUtilTx) csvString += ", " - csvString += dm.time?.formattedDate(format: "yyyy-MM-dd j:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") + csvString += dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "") } } } else if metricsType == 1 { @@ -44,7 +46,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin csvString += ", " csvString += String(dm.current) csvString += ", " - csvString += dm.time?.formattedDate(format: "yyyy-MM-dd j:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") + csvString += dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "") } } } @@ -53,6 +55,8 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin func PositionToCsvFile(positions: [PositionEntity]) -> String { var csvString: String = "" + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma") // Create Position Header csvString = "SeqNo, Latitude, Longitude, Alt, Sats, Speed, Heading, SNR, Timestamp" for pos in positions { @@ -73,7 +77,7 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { csvString += ", " csvString += String(pos.snr) csvString += ", " - csvString += pos.time?.formattedDate(format: "yyyy-MM-dd j:mm:ss") ?? NSLocalizedString("unknown.age", comment: "") + csvString += pos.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "") } return csvString } diff --git a/Meshtastic/Helpers/MeshLogger.swift b/Meshtastic/Helpers/MeshLogger.swift index 8de54f48..51d2a144 100644 --- a/Meshtastic/Helpers/MeshLogger.swift +++ b/Meshtastic/Helpers/MeshLogger.swift @@ -12,9 +12,11 @@ class MeshLogger { guard let logFile = logFile else { return } - + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssSSa", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss.SS a") + let formatter = DateFormatter() - formatter.dateFormat = "M/d/yy h:mm:ss.SSSS" + formatter.dateFormat = dateFormatString let timestamp = formatter.string(from: Date()) guard let data = (message + " - " + timestamp + "\n").data(using: String.Encoding.utf8) else { return } print(message) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index 707abee1..bab02dca 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -32,6 +32,8 @@ struct ChannelMessageList: View { var body: some View { NavigationStack { + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a") ScrollViewReader { scrollView in ScrollView { LazyVStack { @@ -104,7 +106,7 @@ struct ChannelMessageList: View { Menu("message.details") { VStack { let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp)) - Text("Date \(messageDate, style: .date) \(messageDate.formattedDate(format: "h:mm:ss a"))").font(.caption2).foregroundColor(.gray) + Text(" \(messageDate.formattedDate(format: dateFormatString))").foregroundColor(.gray) } if !currentUser { VStack { @@ -127,16 +129,15 @@ struct ChannelMessageList: View { let ackDate = Date(timeIntervalSince1970: TimeInterval(message.ackTimestamp)) let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date()) if ackDate >= sixMonthsAgo! { - Text((ackDate.formattedDate(format: "h:mm:ss a"))).font(.caption2).foregroundColor(.gray) + Text("Ack Time: \(ackDate.formattedDate(format: "h:mm:ss a"))").foregroundColor(.gray) } else { - Text("unknown.age").font(.caption2).foregroundColor(.gray) + Text("unknown.age").foregroundColor(.gray) } } } if message.ackSNR != 0 { VStack { Text("Ack SNR\(String(format: "%.2f", message.ackSNR)) dB") - .font(.caption2) .foregroundColor(.gray) } } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index ec412bd9..06abcafa 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -28,6 +28,8 @@ struct Contacts: View { var body: some View { NavigationSplitView { + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMdd", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY") List { Section(header: Text("channels")) { // Display Contacts for the rest of the non admin channels @@ -65,10 +67,10 @@ struct Contacts: View { Text("Yesterday") .font(.subheadline) } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { - Text(lastMessageTime.formattedDate(format: "MM/dd/yy")) + Text(lastMessageTime.formattedDate(format: dateFormatString)) .font(.subheadline) } else if lastMessageDay < (currentDay - 1800) { - Text(lastMessageTime.formattedDate(format: "MM/dd/yy")) + Text(lastMessageTime.formattedDate(format: dateFormatString)) .font(.subheadline) } } @@ -162,10 +164,10 @@ struct Contacts: View { Text("Yesterday") .font(.subheadline) } else if lastMessageDay < (currentDay - 1) && lastMessageDay > (currentDay - 5) { - Text(lastMessageTime.formattedDate(format: "MM/dd/yy")) + Text(lastMessageTime.formattedDate(format: dateFormatString)) .font(.subheadline) } else if lastMessageDay < (currentDay - 1800) { - Text(lastMessageTime.formattedDate(format: "MM/dd/yy")) + Text(lastMessageTime.formattedDate(format: dateFormatString)) .font(.subheadline) } } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 027971cc..086682a2 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -31,6 +31,8 @@ struct UserMessageList: View { var body: some View { NavigationStack { + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmss", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss:a") ScrollViewReader { scrollView in ScrollView { LazyVStack { @@ -105,8 +107,9 @@ struct UserMessageList: View { } Menu("message.details") { VStack { + let messageDate = Date(timeIntervalSince1970: TimeInterval(message.messageTimestamp)) - Text("Date \(messageDate, style: .date) \(messageDate.formattedDate(format: "h:mm:ss a"))").font(.caption2).foregroundColor(.gray) + Text("\(messageDate.formattedDate(format: dateFormatString))").foregroundColor(.gray) } if !currentUser { VStack { @@ -130,7 +133,7 @@ struct UserMessageList: View { let ackDate = Date(timeIntervalSince1970: TimeInterval(message.ackTimestamp)) let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date()) if ackDate >= sixMonthsAgo! { - Text((ackDate.formattedDate(format: "h:mm:ss a"))).font(.caption2).foregroundColor(.gray) + Text("Ack Time: \(ackDate.formattedDate(format: "h:mm:ss a"))").foregroundColor(.gray) } else { Text("unknown.age").font(.caption2).foregroundColor(.gray) } diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 59b9dcc6..301d2418 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -38,6 +38,8 @@ struct DeviceMetricsLog: View { .frame(height: 150) } } + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { //Add a table for mac and ipad Table(node.telemetries!.reversed() as! [TelemetryEntity]) { @@ -66,9 +68,9 @@ struct DeviceMetricsLog: View { Text("\(String(format: "%.2f", dm.airUtilTx))%") } } - TableColumn("Time Stamp") { dm in + TableColumn("Date & Time") { dm in if dm.metricsType == 0 { - Text(dm.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") + Text(dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) } } } @@ -81,14 +83,14 @@ struct DeviceMetricsLog: View { GridItem(), GridItem(), GridItem(), - GridItem(.fixed(120)) + GridItem(.fixed(135)) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { GridRow { Text("Batt") .font(.caption) .fontWeight(.bold) - Text("Voltage") + Text("Volt") .font(.caption) .fontWeight(.bold) Text("ChUtil") @@ -97,7 +99,7 @@ struct DeviceMetricsLog: View { Text("AirTm") .font(.caption) .fontWeight(.bold) - Text("Timestamp") + Text("Date & Time") .font(.caption) .fontWeight(.bold) } @@ -117,8 +119,9 @@ struct DeviceMetricsLog: View { .font(.caption) Text("\(String(format: "%.2f", dm.airUtilTx))%") .font(.caption) - Text(dm.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") - .font(.caption) + + Text(dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) + .font(.caption2) } } } diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 1f4a3b24..f1a2e5de 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -21,7 +21,8 @@ struct EnvironmentMetricsLog: View { var body: some View { NavigationStack { - + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { //Add a table for mac and ipad Table(node.telemetries!.reversed() as! [TelemetryEntity]) { @@ -55,9 +56,9 @@ struct EnvironmentMetricsLog: View { Text("\(String(format: "%.2f", em.voltage))") } } - TableColumn("Time Stamp") { em in + TableColumn("Date & Time") { em in if em.metricsType == 1 { - Text(em.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") + Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) } } } @@ -68,7 +69,7 @@ struct EnvironmentMetricsLog: View { GridItem(), GridItem(), GridItem(), - GridItem(.fixed(115)) + GridItem(.fixed(125)) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { @@ -86,7 +87,7 @@ struct EnvironmentMetricsLog: View { Text("Gas") .font(.caption) .fontWeight(.bold) - Text("Timestamp") + Text("Date & Time") .font(.caption) .fontWeight(.bold) } @@ -104,8 +105,8 @@ struct EnvironmentMetricsLog: View { .font(.caption) Text("\(String(format: "%.2f", em.gasResistance))") .font(.caption) - Text(em.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") - .font(.caption) + Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) + .font(.caption2) } } } diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 8fe6e582..6a084909 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -21,6 +21,8 @@ struct PositionLog: View { var body: some View { NavigationStack { + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma") if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { //Add a table for mac and ipad @@ -50,7 +52,7 @@ struct PositionLog: View { Text("\(String(format: "%.2f", position.snr)) dB") } TableColumn("Time Stamp") { position in - Text(position.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") + Text(position.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) } } @@ -95,7 +97,7 @@ struct PositionLog: View { .font(.caption2) Text(String(mappin.altitude)) .font(.caption2) - Text(mappin.time?.formattedDate(format: "MM/dd/yy j:mm") ?? "Unknown time") + Text(mappin.time?.formattedDate(format: dateFormatString) ?? "Unknown time") .font(.caption2) } } diff --git a/Meshtastic/Views/Settings/AdminMessageList.swift b/Meshtastic/Views/Settings/AdminMessageList.swift index ac98d635..b2c40f8a 100644 --- a/Meshtastic/Views/Settings/AdminMessageList.swift +++ b/Meshtastic/Views/Settings/AdminMessageList.swift @@ -21,7 +21,8 @@ struct AdminMessageList: View { var user: UserEntity? var body: some View { - + let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmmssa", options: 0, locale: Locale.current) + let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mm:ss a") List { if user != nil { @@ -29,7 +30,7 @@ struct AdminMessageList: View { HStack { - Text("\(am.adminDescription ?? NSLocalizedString("unknown", comment: "Unknown")) - \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)), style: .date) \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)).formattedDate(format: "h:mm:ss a"))") + Text("\(am.adminDescription ?? NSLocalizedString("unknown", comment: "Unknown")) - \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)).formattedDate(format: dateFormatString))") .font(.caption) if am.receivedACK { From ef3a6b89f193e6174a9584638e26a8bb557989a0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 31 Dec 2022 02:23:21 -0800 Subject: [PATCH 21/40] Localize Telemetry Headers --- Meshtastic/Export/WriteCsvFile.swift | 6 ++-- .../Views/Messages/ChannelMessageList.swift | 2 +- .../Views/Messages/UserMessageList.swift | 2 +- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 21 +++++++------- .../Views/Nodes/EnvironmentMetricsLog.swift | 28 ++++--------------- Meshtastic/Views/Nodes/PositionLog.swift | 4 +-- .../Settings/Config/PositionConfig.swift | 2 +- de.lproj/Localizable.strings | 8 ++++++ en.lproj/Localizable.strings | 9 +++++- 9 files changed, 39 insertions(+), 43 deletions(-) diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index 0dbc4d69..31d08e9e 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -13,7 +13,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma") if metricsType == 0 { // Create Device Metrics Header - csvString = "Battery Level, Voltage, Channel Utilization, Airtime, Timestamp" + csvString = "\(NSLocalizedString("battery.level", comment: "")), \(NSLocalizedString("voltage", comment: "")), \(NSLocalizedString("channel.utilization", comment: "")), \(NSLocalizedString("airtime", comment: "")), \(NSLocalizedString("timestamp", comment: ""))" for dm in telemetry{ if dm.metricsType == 0 { csvString += "\n" @@ -30,7 +30,7 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin } } else if metricsType == 1 { // Create Environment Telemetry Header - csvString = "Temperature, Relative Humidity, Barometric Pressure, Gas Resistance, Voltage, Current" + csvString = "Temperature, Relative Humidity, Barometric Pressure, Gas Resistance, \(NSLocalizedString("voltage", comment: "")), \(NSLocalizedString("current", comment: "")), \(NSLocalizedString("timestamp", comment: ""))" for dm in telemetry{ if dm.metricsType == 1 { csvString += "\n" @@ -58,7 +58,7 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { let localeDateFormat = DateFormatter.dateFormat(fromTemplate: "yyMMddjmma", options: 0, locale: Locale.current) let dateFormatString = (localeDateFormat ?? "MM/dd/YY j:mma") // Create Position Header - csvString = "SeqNo, Latitude, Longitude, Alt, Sats, Speed, Heading, SNR, Timestamp" + csvString = "SeqNo, Latitude, Longitude, Altitude, Sats, Speed, Heading, SNR, \(NSLocalizedString("timestamp", comment: ""))" for pos in positions { csvString += "\n" csvString += String(pos.seqNo) diff --git a/Meshtastic/Views/Messages/ChannelMessageList.swift b/Meshtastic/Views/Messages/ChannelMessageList.swift index bab02dca..31f71bbe 100644 --- a/Meshtastic/Views/Messages/ChannelMessageList.swift +++ b/Meshtastic/Views/Messages/ChannelMessageList.swift @@ -137,7 +137,7 @@ struct ChannelMessageList: View { } if message.ackSNR != 0 { VStack { - Text("Ack SNR\(String(format: "%.2f", message.ackSNR)) dB") + Text("Ack SNR: \(String(format: "%.2f", message.ackSNR)) dB") .foregroundColor(.gray) } } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 086682a2..d696714e 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -141,7 +141,7 @@ struct UserMessageList: View { } if message.ackSNR != 0 { VStack { - Text("Ack SNR\(String(format: "%.2f", message.ackSNR)) dB") + Text("Ack SNR: \(String(format: "%.2f", message.ackSNR)) dB") .font(.caption2) .foregroundColor(.gray) } diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 301d2418..ca44e596 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -5,9 +5,7 @@ // Copyright(c) Garth Vander Houwen 7/7/22. // import SwiftUI -#if canImport(Charts) import Charts -#endif struct DeviceMetricsLog: View { @@ -24,7 +22,7 @@ struct DeviceMetricsLog: View { let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) let data = node.telemetries!.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) if data.count > 0 { - GroupBox(label: Label("Battery Level Trend", systemImage: "battery.100")) { + GroupBox(label: Label("battery.level.trend", systemImage: "battery.100")) { Chart(data.array as! [TelemetryEntity], id: \.self) { LineMark( x: .value("Hour", $0.time!.formattedDate(format: "ha")), @@ -43,7 +41,8 @@ struct DeviceMetricsLog: View { if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { //Add a table for mac and ipad Table(node.telemetries!.reversed() as! [TelemetryEntity]) { - TableColumn("Battery Level") { dm in + + TableColumn("battery.level") { dm in if dm.metricsType == 0 { if dm.batteryLevel == 0 { Text("Powered") @@ -53,22 +52,22 @@ struct DeviceMetricsLog: View { } } } - TableColumn("Voltage") { dm in + TableColumn("voltage") { dm in if dm.metricsType == 0 { Text("\(String(format: "%.2f", dm.voltage))") } } - TableColumn("Channel Utilization") { dm in + TableColumn("channel.utilization") { dm in if dm.metricsType == 0 { Text(String(format: "%.2f", dm.channelUtilization)) } } - TableColumn("Airtime") { dm in + TableColumn("airtime") { dm in if dm.metricsType == 0 { Text("\(String(format: "%.2f", dm.airUtilTx))%") } } - TableColumn("Date & Time") { dm in + TableColumn("timestamp") { dm in if dm.metricsType == 0 { Text(dm.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) } @@ -83,7 +82,7 @@ struct DeviceMetricsLog: View { GridItem(), GridItem(), GridItem(), - GridItem(.fixed(135)) + GridItem(.fixed(130)) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { GridRow { @@ -96,10 +95,10 @@ struct DeviceMetricsLog: View { Text("ChUtil") .font(.caption) .fontWeight(.bold) - Text("AirTm") + Text("airtime") .font(.caption) .fontWeight(.bold) - Text("Date & Time") + Text("timestamp") .font(.caption) .fontWeight(.bold) } diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index f1a2e5de..b0f67c58 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -46,17 +46,17 @@ struct EnvironmentMetricsLog: View { Text("\(String(format: "%.2f", em.gasResistance))") } } - TableColumn("Current") { em in + TableColumn("current") { em in if em.metricsType == 1 { Text("\(String(format: "%.2f", em.current))") } } - TableColumn("Voltage") { em in + TableColumn("voltage") { em in if em.metricsType == 1 { Text("\(String(format: "%.2f", em.voltage))") } } - TableColumn("Date & Time") { em in + TableColumn("timestamp") { em in if em.metricsType == 1 { Text(em.time?.formattedDate(format: dateFormatString) ?? NSLocalizedString("unknown.age", comment: "")) } @@ -69,7 +69,7 @@ struct EnvironmentMetricsLog: View { GridItem(), GridItem(), GridItem(), - GridItem(.fixed(125)) + GridItem(.fixed(130)) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { @@ -87,7 +87,7 @@ struct EnvironmentMetricsLog: View { Text("Gas") .font(.caption) .fontWeight(.bold) - Text("Date & Time") + Text("timestamp") .font(.caption) .fontWeight(.bold) } @@ -119,11 +119,8 @@ struct EnvironmentMetricsLog: View { HStack { Button(role: .destructive) { - isPresentingClearLogConfirm = true - } label: { - Label("Clear Log", systemImage: "trash.fill") } .buttonStyle(.bordered) @@ -136,22 +133,15 @@ struct EnvironmentMetricsLog: View { titleVisibility: .visible ) { Button("Delete all environment metrics?", role: .destructive) { - if clearTelemetry(destNum: node.num, metricsType: 1, context: context) { - print("Clear Environment Metrics Log Failed") - } } } - Button { - exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 1) isExporting = true - } label: { - Label("save", systemImage: "square.and.arrow.down") } .buttonStyle(.bordered) @@ -162,13 +152,10 @@ struct EnvironmentMetricsLog: View { .navigationTitle("Environment Metrics Log") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: - ZStack { - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) .onAppear { - self.bleManager.context = context } .fileExporter( @@ -177,15 +164,10 @@ struct EnvironmentMetricsLog: View { contentType: .commaSeparatedText, defaultFilename: String("\(node.user!.longName ?? "Node") Environment Metrics Log"), onCompletion: { result in - if case .success = result { - print("Environment metrics log download succeeded.") - self.isExporting = false - } else { - print("Environment metrics log download failed: \(result).") } } diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 6a084909..0ab8e369 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -65,7 +65,7 @@ struct PositionLog: View { GridItem(.fixed(95)), GridItem(), GridItem(), - GridItem(.fixed(115)) + GridItem(.fixed(130)) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { @@ -83,7 +83,7 @@ struct PositionLog: View { Text("Alt") .font(.caption2) .fontWeight(.bold) - Text("Timestamp") + Text("timestamp") .font(.caption2) .fontWeight(.bold) } diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 1a979dbc..fc203b54 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -155,7 +155,7 @@ struct PositionConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Toggle(isOn: $includeTimestamp) { //128 - Label("Timestamp", systemImage: "clock") + Label("timestamp", systemImage: "clock") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index ddd6abbf..1d008cdb 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -10,12 +10,15 @@ "admin"="admin"; "admin.log"="Admin Message Log"; "ago"="her"; +"airtime"="Airtime"; "always.on"="Immer an"; "app.settings"="App Einstellungen"; "are.you.sure"="Bist Du sicher?"; "ascii.capable"="ASCII fähig"; "available.radios"="Geräte in der Nähe"; "automatic.detection"="Automatische erkennung"; +"battery.level"="Battery Level"; +"battery.level.trend"="Battery Level Trend"; "ble.name"="BLE Name"; "%@ ble.errorcode.6"=" %@! The app will automatically reconnect to the preferred radio if it come back in range."; "%@ ble.errorcode.14"="%&!This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; @@ -37,6 +40,7 @@ "channel.role.disabled"="Deaktiviert"; "channel.role.primary"="Primär"; "channel.role.secondary"="Sekundär"; +"channel.utilization"="Channel Utilization"; "channels"="Kanäle"; "clear.app.data"="App Daten löschen"; "close"="Close"; @@ -47,6 +51,7 @@ "connecting"="Verbinde..."; "contacts"="Kontakte"; "copy"="Kopieren"; +"current"="Current"; "default"="Standard"; "delete"="Löschen"; "device"="Gerät"; @@ -197,6 +202,8 @@ "telemetry"="Telemetrie (Sensoren)"; "telemetry.config"="Telemetrie Einstellungen"; "timeout"="Zeitlimit erreicht"; +"timeout"="Zeitlimit erreicht"; +"timestamp"="Timestamp"; "twitter"="Twitter"; "unknown"="Unknown"; "unknown.age"="Unbekanntes alter"; @@ -205,4 +212,5 @@ "update.interval"="Update intervall"; "user"="Benutzer"; "user.details"="Benutzer Details"; +"voltage"="Voltage"; "waiting"="Warte..."; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index fa2ca147..edd7dd0d 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -10,12 +10,15 @@ "admin"="Admin"; "admin.log"="Admin Message Log"; "ago"="ago"; +"airtime"="Airtime"; "always.on"="Always On"; "app.settings"="App Settings"; "are.you.sure"="Are you sure?"; "ascii.capable"="ASCII Capable"; "available.radios"="Available Radios"; "automatic.detection"="Automatic Detection"; +"battery.level"="Battery Level"; +"battery.level.trend"="Battery Level Trend"; "ble.name"="BLE Name"; "bluetooth"="Bluetooth"; "bluetooth.config"="Bluetooth Config"; @@ -34,6 +37,7 @@ "channel.role.disabled"="Disabled"; "channel.role.primary"="Primary"; "channel.role.secondary"="Secondary"; +"channel.utilization"="Channel Utilization"; "channels"="Channels"; "clear.app.data"="Clear App Data"; "close"="Close"; @@ -44,6 +48,7 @@ "connecting"="Connecting . ."; "contacts"="Contacts"; "copy"="Copy"; +"current"="Current"; "default"="Default"; "delete"="Delete"; "device"="Device"; @@ -196,7 +201,8 @@ "tapback.poop"="Poop"; "telemetry"="Telemetry (Sensors)"; "telemetry.config"="Telemetry Config"; -"timeout"="timeout"; +"timeout"="Timeout"; +"timestamp"="Timestamp"; "twitter"="Twitter"; "unknown"="Unknown"; "unknown.age"="Unknown Age"; @@ -205,4 +211,5 @@ "update.interval"="Update Interval"; "user"="User"; "user.details"="User Details"; +"voltage"="Voltage"; "waiting"="Waiting. . ."; From 1f19d0c31f1ddd711fe7df9dd1188b753415d8e3 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 1 Jan 2023 14:48:50 -0800 Subject: [PATCH 22/40] Localize Mesh Log messages for the BLE manager --- Meshtastic/Helpers/BLEManager.swift | 68 ++++++++++++------- Meshtastic/Helpers/MeshPackets.swift | 8 ++- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 4 +- .../Views/Nodes/EnvironmentMetricsLog.swift | 7 +- Meshtastic/Views/Nodes/NodeDetail.swift | 4 +- Meshtastic/Views/Nodes/PositionLog.swift | 8 +-- .../Settings/Config/BluetoothConfig.swift | 4 +- de.lproj/Localizable.strings | 26 +++++-- en.lproj/Localizable.strings | 23 ++++++- 9 files changed, 104 insertions(+), 48 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index cbe4fbd5..be6bced7 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -93,7 +93,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { self.timeoutTimerCount += 1 self.lastConnectionError = "" - if timeoutTimerCount == 10 { + if timeoutTimerCount == 2 { if connectedPeripheral != nil { self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) } @@ -104,7 +104,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } self.isConnected = false self.isConnecting = false - self.lastConnectionError = "🚨 Connection failed after \(timeoutTimerCount) attempts to connect to \(name). You may need to forget your device under Settings > Bluetooth." + self.lastConnectionError = "🚨 " + String.localizedStringWithFormat(NSLocalizedString("ble.connection.timeout %d %@", + comment: "Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."), + timeoutTimerCount, name) + MeshLogger.log(lastConnectionError) self.timeoutTimerCount = 0 self.timeoutTimerRuns += 1 @@ -206,7 +209,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let errorCode = (e as NSError).code if errorCode == 6 { // CBError.Code.connectionTimeout The connection has timed out unexpectedly. // Happens when device is manually reset / powered off - lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ ble.errorcode.6", + lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("ble.errorcode.6 %@", comment: "The app will automatically reconnect to the preferred radio if it come back in range."), e.localizedDescription) print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") @@ -214,18 +217,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { // Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work. lastConnectionError = "🚨 \(e.localizedDescription)" print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") - } else if errorCode == 14 { // Peer removed pairing information // Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that - lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ ble.errorcode.14", + lastConnectionError = "🚨 " + String.localizedStringWithFormat(NSLocalizedString("ble.errorcode.14 %@", comment: "This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."), e.localizedDescription) print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(lastConnectionError)") } else { - lastConnectionError = "🚨" + e.localizedDescription + lastConnectionError = "🚨 \(e.localizedDescription)" print("🚨 BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") } - } else { // Disconnected without error which indicates user intent to disconnect // Happens when swiping to disconnect @@ -291,7 +292,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return } - MeshLogger.log("🛎️ Requesting Device Metadata for \(connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") + let nodeName = connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: NSLocalizedString("unknown", comment: "Unknown")) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.devicemetadata %@", + comment: "Requesting Device Metadata for %@"), nodeName) + MeshLogger.log("🛎️ \(logString)") var adminPacket = AdminMessage() adminPacket.getDeviceMetadataRequest = true var meshPacket: MeshPacket = MeshPacket() @@ -335,7 +339,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - MeshLogger.log("🪧 Sent a Trace Route Request to node: \(destNum).") + + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.sent %d", + comment: "Sent a Trace Route Request to node: %d"), destNum) + MeshLogger.log("🪧 \(logString)") } return success } @@ -344,11 +351,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { guard (connectedPeripheral!.peripheral.state == CBPeripheralState.connected) else { return } if FROMRADIO_characteristic == nil { - MeshLogger.log("🚨 Unsupported Firmware Version Detected, unable to connect to device.") + MeshLogger.log("🚨 \(NSLocalizedString("firmware.version.unsupported", comment: "Unsupported Firmware Version Detected, unable to connect to device."))") invalidVersion = true return } else { - MeshLogger.log("🛎️ Issuing Want Config to \(connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: "Unknown"))") + + let nodeName = connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: NSLocalizedString("unknown", comment: "Unknown")) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.wantconfig %@", + comment: "Issuing Want Config to %@"), nodeName) + MeshLogger.log("🛎️ \(logString)") //BLE Characteristics discovered, issue wantConfig var toRadio: ToRadio = ToRadio() configNonce += 1 @@ -379,7 +390,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { // BLE PIN connection errors // 5 CBATTErrorDomain Code=5 "Authentication is insufficient." // 15 CBATTErrorDomain Code=15 "Encryption is insufficient." - lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("%@ ble.errorcode.pin", + lastConnectionError = "🚨" + String.localizedStringWithFormat(NSLocalizedString("ble.errorcode.pin %@", comment: "Please try connecting again and check the PIN carefully."), e.localizedDescription) print("🚨 \(e.localizedDescription) Please try connecting again and check the PIN carefully.") @@ -612,7 +623,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil { connectTo(peripheral: preferredPeripheral!.peripheral) } - MeshLogger.log("🚫 Message Send Failed, not properly connected to \(preferredPeripheral?.name ?? NSLocalizedString("unknown", comment: "Unknown"))") + let nodeName = connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: NSLocalizedString("unknown", comment: "Unknown")) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.textmessage.send.failed %@", + comment: "Message Send Failed, not properly connected to %@"), nodeName) + MeshLogger.log("🚫 \(logString)") + success = false } else if message.count < 1 { @@ -682,15 +697,14 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var toRadio: ToRadio! toRadio = ToRadio() toRadio.packet = meshPacket - let binaryData: Data = try! toRadio.serializedData() - MeshLogger.log("📲 New messageId \(newMessage.messageId) sent to \(newMessage.toUser?.longName! ?? NSLocalizedString("unknown", comment: "Unknown"))") if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - MeshLogger.log("💬 Sent a message from \(connectedPeripheral.num) to \(toUserNum)") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.textmessage.sent %d %d %d", + comment: "Sent message %d from %d to %d"), newMessage.messageId, fromUserNum, toUserNum) + MeshLogger.log("💬 \(logString)") do { - try context!.save() print("💾 Saved a new sent message from \(connectedPeripheral.num) to \(toUserNum)") success = true @@ -735,8 +749,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - - MeshLogger.log("📍 Sent a Waypoint Packet from: \(fromNodeNum)") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.waypoint.sent %d", + comment: "Sent a Waypoint Packet from: %@d"), fromNodeNum) + MeshLogger.log("📍 \(logString)") if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) @@ -785,7 +800,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - MeshLogger.log("📍 Sent a Position Packet from the Apple device GPS to node: \(fromNodeNum)") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.sharelocation %d", + comment: "Sent a Position Packet from the Apple device GPS to node: %@d"), fromNodeNum) + MeshLogger.log("📍 \(logString)") } return success } @@ -996,7 +1013,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { adminPacket.setChannel = chan var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(connectedPeripheral.num) - meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.from = UInt32(connectedPeripheral.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. = NSFetchRequest.init(entityName: "UserEntity") messageUsers.predicate = NSPredicate(format: "num IN %@", [packet.to, packet.from]) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index ca44e596..a6ac960b 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -82,7 +82,7 @@ struct DeviceMetricsLog: View { GridItem(), GridItem(), GridItem(), - GridItem(.fixed(130)) + GridItem(.fixed(140)) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { GridRow { @@ -95,7 +95,7 @@ struct DeviceMetricsLog: View { Text("ChUtil") .font(.caption) .fontWeight(.bold) - Text("airtime") + Text("AirTm") .font(.caption) .fontWeight(.bold) Text("timestamp") diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index b0f67c58..e044a08f 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -41,7 +41,7 @@ struct EnvironmentMetricsLog: View { Text("\(String(format: "%.2f", em.barometricPressure))") } } - TableColumn("Gas Resistance") { em in + TableColumn("gas.resistance") { em in if em.metricsType == 1 { Text("\(String(format: "%.2f", em.gasResistance))") } @@ -69,12 +69,11 @@ struct EnvironmentMetricsLog: View { GridItem(), GridItem(), GridItem(), - GridItem(.fixed(130)) + GridItem(.fixed(140)) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { GridRow { - Text("Temp") .font(.caption) .fontWeight(.bold) @@ -84,7 +83,7 @@ struct EnvironmentMetricsLog: View { Text("Bar") .font(.caption) .fontWeight(.bold) - Text("Gas") + Text("gas") .font(.caption) .fontWeight(.bold) Text("timestamp") diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index e41cae3e..932ae13a 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -197,11 +197,11 @@ struct NodeDetail: View { Divider() VStack { if node.user != nil { - Image(node.user!.hwModel ?? "UNSET") + Image(node.user!.hwModel ?? NSLocalizedString("unset", comment: "Unset")) .resizable() .frame(width: 75, height: 75) .cornerRadius(5) - Text(String(node.user!.hwModel ?? "UNSET")) + Text(String(node.user!.hwModel ?? NSLocalizedString("unset", comment: "Unset"))) .font(.callout).fixedSize() } } diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index 0ab8e369..2be8210c 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -61,11 +61,11 @@ struct PositionLog: View { ScrollView { // Use a grid on iOS as a table only shows a single column let columns = [ + GridItem(.fixed(90)), GridItem(.fixed(95)), - GridItem(.fixed(95)), - GridItem(), - GridItem(), - GridItem(.fixed(130)) + GridItem(.fixed(45)), + GridItem(.fixed(40)), + GridItem(.fixed(140)) ] LazyVGrid(columns: columns, alignment: .leading, spacing: 1) { diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 57e2da92..16afc341 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -68,7 +68,7 @@ struct BluetoothConfig: View { } .keyboardType(.decimalPad) if shortPin { - Text("BLE Pin must be 6 digits long.") + Text("bluetooth.pin.validation") .font(.callout) .foregroundColor(.red) } @@ -92,7 +92,7 @@ struct BluetoothConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")") { + Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown"))") { var bc = Config.BluetoothConfig() bc.enabled = enabled bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 1d008cdb..796b8097 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -19,16 +19,18 @@ "automatic.detection"="Automatische erkennung"; "battery.level"="Battery Level"; "battery.level.trend"="Battery Level Trend"; -"ble.name"="BLE Name"; -"%@ ble.errorcode.6"=" %@! The app will automatically reconnect to the preferred radio if it come back in range."; -"%@ ble.errorcode.14"="%&!This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; -"%@ ble.errorcode.pin"=" %@! Please try connecting again and check the PIN carefully."; +"ble.name"="BLE Name";"ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."; +"ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."; +"ble.errorcode.6 %@"="%@ The app will automatically reconnect to the preferred radio if it come back in range."; +"ble.errorcode.14 %@"="%@ This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; +"ble.errorcode.pin %@"="%@ Please try connecting again and check the PIN carefully."; "bluetooth"="Bluetooth"; "bluetooth.config"="Bluetooth Konfiguration"; "bluetooth.mode.randompin"="Zufällige PIN"; "bluetooth.mode.fixedpin"="Feste PIN"; "bluetooth.mode.nopin"="Keine PIN (geht einfach)"; "bluetooth.pairingmode"="Pairing Mode"; +"bluetooth.pin.validation"="BLE Pin must be 6 digits long."; "bytes"="Bytes"; "cancel"="Abbrechen"; "canned.messages"="Canned Messages"; @@ -73,6 +75,9 @@ "external.notification"="Externe Benachrichtigung"; "external.notification.config"="Einstellungen der externen Benachrichtigung"; "firmware.version"="Firmware Version"; +"firmware.version.unsupported"="Unsupported Firmware Version Detected, unable to connect to device."; +"gas"="Gas"; +"gas.resistance"="Gas Resistance"; "generate.qr.code"="Generate QR Code"; "gpsformat.dec"="Dezimalgrad Format"; "gpsformat.dms"="Grad Minuten Sekunden"; @@ -127,6 +132,18 @@ "map"="Mesh Karte"; "map.type"="kartentyp"; "mesh.log"="Mesh Log"; +"mesh.log.cannedmessages.messages.get %d"="Requested Canned Messages Module Messages for node: %d"; +"mesh.log.channel.sent %d"="Sent a Channel for: %d Channel Index %d"; +"mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; +"mesh.log.lora.config.sent %d"="Sent a LoRa.Config for: %d"; +"mesh.log.sharelocation %d"="Sent a Position Packet from the Apple device GPS to node: %@d"; +"mesh.log.telemetry.received %d"="Telemetry received for: %d"; +"mesh.log.textmessage.received"="Message received from the text message app."; +"mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; +"mesh.log.textmessage.sent %d %d %d"="Sent message %d from %d to %d"; +"mesh.log.traceroute.sent %d"="Sent a Trace Route Request to node: %d"; +"mesh.log.wantconfig %@"="Issuing Want Config to %@"; +"mesh.log.waypoint.sent %d"="Sent a Waypoint Packet from: %@d"; "message"="Nachricht"; "message.details"="Nachrichtendetails"; "messages"="Nachrichten"; @@ -202,7 +219,6 @@ "telemetry"="Telemetrie (Sensoren)"; "telemetry.config"="Telemetrie Einstellungen"; "timeout"="Zeitlimit erreicht"; -"timeout"="Zeitlimit erreicht"; "timestamp"="Timestamp"; "twitter"="Twitter"; "unknown"="Unknown"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index edd7dd0d..0da43ed1 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -20,12 +20,17 @@ "battery.level"="Battery Level"; "battery.level.trend"="Battery Level Trend"; "ble.name"="BLE Name"; +"ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."; +"ble.errorcode.6 %@"="%@ The app will automatically reconnect to the preferred radio if it come back in range."; +"ble.errorcode.14 %@"="%@ This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; +"ble.errorcode.pin %@"="%@ Please try connecting again and check the PIN carefully."; "bluetooth"="Bluetooth"; "bluetooth.config"="Bluetooth Config"; "bluetooth.mode.randompin"="Random PIN"; "bluetooth.mode.fixedpin"="Fixed PIN"; "bluetooth.mode.nopin"="No PIN (Just Works)"; "bluetooth.pairingmode"="Pairing Mode"; +"bluetooth.pin.validation"="BLE Pin must be 6 digits long."; "bytes"="Bytes"; "cancel"="Cancel"; "canned.messages"="Canned Messages"; @@ -70,6 +75,9 @@ "external.notification"="External Notification"; "external.notification.config"="External Notification Config"; "firmware.version"="Firmware Version"; +"firmware.version.unsupported"="Unsupported Firmware Version Detected, unable to connect to device."; +"gas"="Gas"; +"gas.resistance"="Gas Resistance"; "generate.qr.code"="Generate QR Code"; "gpsformat.dec"="Decimal Degrees Format"; "gpsformat.dms"="Degrees Minutes Seconds"; @@ -124,9 +132,18 @@ "map"="Mesh Map"; "map.type"="Map Type"; "mesh.log"="Mesh Log"; -"%@ ble.errorcode.6"=" %@! The app will automatically reconnect to the preferred radio if it come back in range."; -"%@ ble.errorcode.14"="This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; -"%@ ble.errorcode.pin"=" %@! Please try connecting again and check the PIN carefully."; +"mesh.log.cannedmessages.messages.get %d"="Requested Canned Messages Module Messages for node: %d"; +"mesh.log.channel.sent %d"="Sent a Channel for: %d Channel Index %d"; +"mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; +"mesh.log.lora.config.sent %d"="Sent a LoRa.Config for: %d"; +"mesh.log.sharelocation %d"="Sent a Position Packet from the Apple device GPS to node: %@d"; +"mesh.log.telemetry.received %d"="Telemetry received for: %d"; +"mesh.log.textmessage.received"="Message received from the text message app."; +"mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; +"mesh.log.textmessage.sent %d %d %d"="Sent message %d from %d to %d"; +"mesh.log.traceroute.sent %d"="Sent a Trace Route Request to node: %d"; +"mesh.log.wantconfig %@"="Issuing Want Config to %@"; +"mesh.log.waypoint.sent %d"="Sent a Waypoint Packet from: %@d"; "message"="Message"; "message.details"="Message Details"; "messages"="Messages"; From de2e48704e802e5f5463cbf91b0a787e4e5c6b6a Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 1 Jan 2023 22:34:58 -0800 Subject: [PATCH 23/40] Mesh log translation updates --- Meshtastic/Helpers/BLEManager.swift | 47 ++++++++-------- Meshtastic/Helpers/MeshPackets.swift | 70 ++++++++++++++---------- Meshtastic/Views/Settings/Settings.swift | 2 - de.lproj/Localizable.strings | 32 ++++++++--- en.lproj/Localizable.strings | 32 ++++++++--- 5 files changed, 112 insertions(+), 71 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index be6bced7..6ee4e5b7 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -340,8 +340,8 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.sent %d", - comment: "Sent a Trace Route Request to node: %d"), destNum) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.sent %@", + comment: "Sent a Trace Route Request to node: %@"), String(destNum)) MeshLogger.log("🪧 \(logString)") } return success @@ -495,16 +495,16 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } // Log any other unknownApp calls - if !nowKnown { MeshLogger.log("🌐 MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") } + if !nowKnown { MeshLogger.log("🕸️ MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") } case .textMessageApp: textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) case .remoteHardwareApp: - MeshLogger.log("🌐 MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .positionApp: positionPacket(packet: decodedInfo.packet, context: context!) case .waypointApp: - MeshLogger.log("🌐 MESH PACKET received for Waypoint App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Waypoint App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .nodeinfoApp: if !invalidVersion { nodeInfoAppPacket(packet: decodedInfo.packet, context: context!) } case .routingApp: @@ -512,29 +512,29 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { case .adminApp: adminAppPacket(packet: decodedInfo.packet, context: context!) case .replyApp: - MeshLogger.log("🌐 MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Reply App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .ipTunnelApp: - MeshLogger.log("🌐 MESH PACKET received for IP Tunnel App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for IP Tunnel App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .serialApp: - MeshLogger.log("🌐 MESH PACKET received for Serial App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Serial App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .storeForwardApp: - MeshLogger.log("🌐 MESH PACKET received for Store Forward App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Store Forward App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .rangeTestApp: - MeshLogger.log("🌐 MESH PACKET received for Range Test App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Range Test App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .telemetryApp: if !invalidVersion { telemetryPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), context: context!) } case .textMessageCompressedApp: - MeshLogger.log("🌐 MESH PACKET received for Text Message Compressed App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Text Message Compressed App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .zpsApp: - MeshLogger.log("🌐 MESH PACKET received for ZPS App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for ZPS App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .privateApp: - MeshLogger.log("🌐 MESH PACKET received for Private App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Private App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .atakForwarder: - MeshLogger.log("🌐 MESH PACKET received for ATAK Forwarder App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for ATAK Forwarder App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .simulatorApp: - MeshLogger.log("🌐 MESH PACKET received for Simulator App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Simulator App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .audioApp: - MeshLogger.log("🌐 MESH PACKET received for Audio App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Audio App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .tracerouteApp: if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { @@ -551,7 +551,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } } case .UNRECOGNIZED(_): - MeshLogger.log("🌐 MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") + MeshLogger.log("🕸️ MESH PACKET received for Other App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .max: print("MAX PORT NUM OF 511") } @@ -701,8 +701,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.textmessage.sent %d %d %d", - comment: "Sent message %d from %d to %d"), newMessage.messageId, fromUserNum, toUserNum) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.textmessage.sent %@ %@ %@", comment: "Sent message %@ from %@ to %@"), String(newMessage.messageId), String(fromUserNum), String(toUserNum)) MeshLogger.log("💬 \(logString)") do { try context!.save() @@ -749,8 +748,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { toRadio = ToRadio() toRadio.packet = meshPacket let binaryData: Data = try! toRadio.serializedData() - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.waypoint.sent %d", - comment: "Sent a Waypoint Packet from: %@d"), fromNodeNum) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.waypoint.sent %@", comment: "Sent a Waypoint Packet from: %@"), String(fromNodeNum)) MeshLogger.log("📍 \(logString)") if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { @@ -800,8 +798,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) success = true - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.sharelocation %d", - comment: "Sent a Position Packet from the Apple device GPS to node: %@d"), fromNodeNum) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.sharelocation %@", comment: "Sent a Position Packet from the Apple device GPS to node: %@"), String(fromNodeNum)) MeshLogger.log("📍 \(logString)") } return success @@ -1052,7 +1049,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse) - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.lora.config.sent %d", comment: "Sent a LoRaConfig for: %d"), connectedPeripheral.num) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.lora.config.sent %@", comment: "Sent a LoRaConfig for: %@"), connectedPeripheral.num) MeshLogger.log("📻 \(logString)") } return true @@ -1358,7 +1355,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.get %d", comment: "Requested Canned Messages Module Messages for node: %d"), connectedPeripheral.num) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.get %@", comment: "Requested Canned Messages Module Messages for node: %@"), String(connectedPeripheral.num)) MeshLogger.log("🥫 \(logString)") return true } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 1c265968..fbf26ac9 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -41,7 +41,8 @@ func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64 // We don't care about any of the Power settings, config is available for everyting else if config.payloadVariant == Config.OneOf_PayloadVariant.bluetooth(config.bluetooth) { - MeshLogger.log("🖥️ Bluetooth config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.bluetooth.config %@", comment: "Bluetooth config received: %@"), String(nodeNum)) + MeshLogger.log("📶 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -81,7 +82,8 @@ func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64 if config.payloadVariant == Config.OneOf_PayloadVariant.device(config.device) { - MeshLogger.log("📟 Device config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.device.config %@", comment: "Device config received: %@"), String(nodeNum)) + MeshLogger.log("📟 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -122,7 +124,8 @@ func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64 if config.payloadVariant == Config.OneOf_PayloadVariant.display(config.display) { - MeshLogger.log("🖥️ Display config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.display.config %@", comment: "Display config received: %@"), String(nodeNum)) + MeshLogger.log("🖥️ \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -181,7 +184,8 @@ func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64 if config.payloadVariant == Config.OneOf_PayloadVariant.lora(config.lora) { - MeshLogger.log("📻 LoRa config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.lora.config %@", comment: "LoRa config received: %@"), String(nodeNum)) + MeshLogger.log("📻 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -237,7 +241,9 @@ func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64 if config.payloadVariant == Config.OneOf_PayloadVariant.network(config.network) { - MeshLogger.log("📶 Network config received \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.network.config %@", comment: "Network config received: %@"), String(nodeNum)) + MeshLogger.log("🌐 \(logString)") + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -270,13 +276,14 @@ func localConfig (config: Config, context:NSManagedObjectContext, nodeNum: Int64 } } catch { let nsError = error as NSError - print("💥 Fetching node for core data WiFiConfigEntity failed: \(nsError)") + print("💥 Fetching node for core data NetworkConfigEntity failed: \(nsError)") } } if config.payloadVariant == Config.OneOf_PayloadVariant.position(config.position) { - MeshLogger.log("🗺️ Position config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.position.config %@", comment: "Positon config received: %@"), String(nodeNum)) + MeshLogger.log("🗺️ \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -327,7 +334,8 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(config.cannedMessage) { - MeshLogger.log("🥫 Canned Message module config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessage.config %@", comment: "Canned Message module config received: %@"), String(nodeNum)) + MeshLogger.log("🥫 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -389,7 +397,9 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.externalNotification(config.externalNotification) { - MeshLogger.log("🚨 External Notifiation module config received: \(String(nodeNum))") + + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.externalnotification.config %@", comment: "External Notifiation module config received: %@"), String(nodeNum)) + MeshLogger.log("📣 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -454,7 +464,8 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.mqtt(config.mqtt) { - MeshLogger.log("🌐 MQTT module config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.mqtt.config %@", comment: "MQTT module config received: %@"), String(nodeNum)) + MeshLogger.log("🌉 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -501,7 +512,8 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.rangeTest(config.rangeTest) { - MeshLogger.log("⛰️ Range Test module config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.rangetest.config %@", comment: "Range Test module config received: %@"), String(nodeNum)) + MeshLogger.log("⛰️ \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -541,7 +553,8 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.serial(config.serial) { - MeshLogger.log("🤖 Serial module config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.serial.config %@", comment: "Serial module config received: %@"), String(nodeNum)) + MeshLogger.log("🤖 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -601,7 +614,8 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum if config.payloadVariant == ModuleConfig.OneOf_PayloadVariant.telemetry(config.telemetry) { - MeshLogger.log("📈 Telemetry module config received: \(String(nodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.telemetry.config %@", comment: "Telemetry module config received: %@"), String(nodeNum)) + MeshLogger.log("📈 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeNum)) @@ -656,7 +670,9 @@ func moduleConfig (config: ModuleConfig, context:NSManagedObjectContext, nodeNum func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedObjectContext) -> MyInfoEntity? { - MeshLogger.log("ℹ️ MyInfo received: \(String(myInfo.myNodeNum))") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.myinfo %@", comment: "MyInfo received: %@"), String(myInfo.myNodeNum)) + MeshLogger.log("ℹ️ \(logString)") + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum)) @@ -721,10 +737,12 @@ func myInfoPacket (myInfo: MyNodeInfo, peripheralId: String, context: NSManagedO func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectContext) { if channel.isInitialized && channel.hasSettings && channel.role != Channel.Role.disabled { + + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.channel.received %d %@", comment: "Channel %d received from: %@"), channel.index, String(fromNum)) + MeshLogger.log("🎛️ \(logString)") - MeshLogger.log("🎛️ Channel received: \(channel.settings.name)") let fetchedMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") - fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum) + fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", channel.index, String(fromNum)) do { @@ -764,7 +782,8 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObjectContext) -> NodeInfoEntity? { - MeshLogger.log("📟 Node info received for: \(nodeInfo.num)") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), String(nodeInfo.num)) + MeshLogger.log("📟 \(logString)") let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num)) @@ -913,7 +932,8 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - MeshLogger.log("📟 Node info received for: \(packet.from)") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), packet.from) + MeshLogger.log("📟 \(logString)") let fetchNodeInfoAppRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) @@ -967,9 +987,11 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.received %@", comment: "Canned Messages Messages Received For: %@"), String(packet.from)) + MeshLogger.log("🥫 \(logString)") + if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { - MeshLogger.log("🥫 Canned Messages Messages Received For: \(packet.from)") let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) @@ -1012,7 +1034,6 @@ func positionPacket (packet: MeshPacket, context: NSManagedObjectContext) { if fetchedNode.count == 1 { let position = PositionEntity(context: context) - position.snr = packet.rxSnr position.seqNo = Int32(positionMessage.seqNumber) position.latitudeI = positionMessage.latitudeI @@ -1026,23 +1047,18 @@ func positionPacket (packet: MeshPacket, context: NSManagedObjectContext) { } else { position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) } - let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet mutablePositions.add(position) - fetchedNode[0].id = Int64(packet.from) fetchedNode[0].num = Int64(packet.from) fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) fetchedNode[0].snr = packet.rxSnr fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet - do { try context.save() print("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") } catch { - context.rollback() - let nsError = error as NSError print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") } @@ -1126,9 +1142,7 @@ func telemetryPacket(packet: MeshPacket, connectedNode: Int64, context: NSManage // Only log telemetry from the mesh not the connected device if connectedNode != Int64(packet.from) { - - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.telemetry.received %d", - comment: "Telemetry received for: %d"), packet.from) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.telemetry.received %@", comment: "Telemetry received for: %@"), String(packet.from)) MeshLogger.log("📈 \(logString)") } diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index 1ec48400..b48411ea 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -44,7 +44,6 @@ struct Settings: View { Image(systemName: "person.crop.rectangle.fill") .symbolRenderingMode(.hierarchical) - Text("user") } @@ -55,7 +54,6 @@ struct Settings: View { Image(systemName: "dot.radiowaves.left.and.right") .symbolRenderingMode(.hierarchical) - Text("lora") } diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 796b8097..0fd8411d 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -132,18 +132,34 @@ "map"="Mesh Karte"; "map.type"="kartentyp"; "mesh.log"="Mesh Log"; -"mesh.log.cannedmessages.messages.get %d"="Requested Canned Messages Module Messages for node: %d"; -"mesh.log.channel.sent %d"="Sent a Channel for: %d Channel Index %d"; +"mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; +"mesh.log.cannedmessage.config %@"="Canned Message module config received: %@"; +"mesh.log.cannedmessages.messages.get %@"="Requested Canned Messages Module Messages for node: %@"; +"mesh.log.cannedmessages.messages.received %@"="Canned Messages Messages Received For: %@"; +"mesh.log.channel.sent %@ %d"="Sent a Channel for: %@ Channel Index %d"; +"mesh.log.channel.received %d %@"="Channel %d received from: %@"; +"mesh.log.device.config %@"="Device config received: %@"; +"mesh.log.display.config %@"="Display config received: %@"; "mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; -"mesh.log.lora.config.sent %d"="Sent a LoRa.Config for: %d"; -"mesh.log.sharelocation %d"="Sent a Position Packet from the Apple device GPS to node: %@d"; -"mesh.log.telemetry.received %d"="Telemetry received for: %d"; +"mesh.log.externalnotification.config %@"="External Notifiation module config received: %@"; +"mesh.log.lora.config %@"="LoRa config received: %@"; +"mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@"; +"mesh.log.mqtt.config %@"="MQTT module config received: %@"; +"mesh.log.myinfo %@"="MyInfo received: %@"; +"mesh.log.network.config %@"="Network config received: %@"; +"mesh.log.nodeinfo.received %@"="Node info received for: %@"; +"mesh.log.position.config %@"="Positon config received: %@"; +"mesh.log.rangetest.config %@"="Range Test module config received: %@"; +"mesh.log.serial.config %@"="Serial module config received: %@"; +"mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; +"mesh.log.telemetry.config %@"="Telemetry module config received: %@"; +"mesh.log.telemetry.received %@"="Telemetry received for: %@"; "mesh.log.textmessage.received"="Message received from the text message app."; "mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; -"mesh.log.textmessage.sent %d %d %d"="Sent message %d from %d to %d"; -"mesh.log.traceroute.sent %d"="Sent a Trace Route Request to node: %d"; +"mesh.log.textmessage.sent %@ %@ %@"="Sent message %@ from %@ to %@"; +"mesh.log.traceroute.sent %@"="Sent a Trace Route Request to node: %@"; "mesh.log.wantconfig %@"="Issuing Want Config to %@"; -"mesh.log.waypoint.sent %d"="Sent a Waypoint Packet from: %@d"; +"mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@"; "message"="Nachricht"; "message.details"="Nachrichtendetails"; "messages"="Nachrichten"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 0da43ed1..fef9802c 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -132,18 +132,34 @@ "map"="Mesh Map"; "map.type"="Map Type"; "mesh.log"="Mesh Log"; -"mesh.log.cannedmessages.messages.get %d"="Requested Canned Messages Module Messages for node: %d"; -"mesh.log.channel.sent %d"="Sent a Channel for: %d Channel Index %d"; +"mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; +"mesh.log.cannedmessage.config %@"="Canned Message module config received: %@"; +"mesh.log.cannedmessages.messages.get %@"="Requested Canned Messages Module Messages for node: %@"; +"mesh.log.cannedmessages.messages.received %@"="Canned Messages Messages Received For: %@"; +"mesh.log.channel.sent %@ %d"="Sent a Channel for: %@ Channel Index %d"; +"mesh.log.channel.received %d %@"="Channel %d received from: %@"; +"mesh.log.device.config %@"="Device config received: %@"; +"mesh.log.display.config %@"="Display config received: %@"; "mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; -"mesh.log.lora.config.sent %d"="Sent a LoRa.Config for: %d"; -"mesh.log.sharelocation %d"="Sent a Position Packet from the Apple device GPS to node: %@d"; -"mesh.log.telemetry.received %d"="Telemetry received for: %d"; +"mesh.log.externalnotification.config %@"="External Notifiation module config received: %@"; +"mesh.log.lora.config %@"="LoRa config received: %@"; +"mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@"; +"mesh.log.mqtt.config %@"="MQTT module config received: %@"; +"mesh.log.myinfo %@"="MyInfo received: %@"; +"mesh.log.network.config %@"="Network config received: %@"; +"mesh.log.nodeinfo.received %@"="Node info received for: %@"; +"mesh.log.position.config %@"="Positon config received: %@"; +"mesh.log.rangetest.config %@"="Range Test module config received: %@"; +"mesh.log.serial.config %@"="Serial module config received: %@"; +"mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; +"mesh.log.telemetry.config %@"="Telemetry module config received: %@"; +"mesh.log.telemetry.received %@"="Telemetry received for: %@"; "mesh.log.textmessage.received"="Message received from the text message app."; "mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; -"mesh.log.textmessage.sent %d %d %d"="Sent message %d from %d to %d"; -"mesh.log.traceroute.sent %d"="Sent a Trace Route Request to node: %d"; +"mesh.log.textmessage.sent %@ %@ %@"="Sent message %@ from %@ to %@"; +"mesh.log.traceroute.sent %@"="Sent a Trace Route Request to node: %@"; "mesh.log.wantconfig %@"="Issuing Want Config to %@"; -"mesh.log.waypoint.sent %d"="Sent a Waypoint Packet from: %@d"; +"mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@"; "message"="Message"; "message.details"="Message Details"; "messages"="Messages"; From e11f1b7f89d69276fb638f55a0772b083471db5d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 1 Jan 2023 22:58:54 -0800 Subject: [PATCH 24/40] Last couple localized mesh log strings --- Meshtastic/Helpers/BLEManager.swift | 10 +++++++--- Meshtastic/Helpers/MeshPackets.swift | 8 ++++++-- de.lproj/Localizable.strings | 4 ++++ en.lproj/Localizable.strings | 4 ++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 6ee4e5b7..0369d2a4 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -539,15 +539,19 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { if let routingMessage = try? RouteDiscovery(serializedData: decodedInfo.packet.decoded.payload) { if routingMessage.route.count == 0 { - MeshLogger.log("🪧 Trace Route request sent to \(decodedInfo.packet.from) was recieived directly.") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.received.direct %@", + comment: "Trace Route request sent to node: %@ was recieived directly."), String(decodedInfo.packet.from)) + MeshLogger.log("🪧 \(logString)") } else { - var routeString = "🪧 Trace Route request returned: \(decodedInfo.packet.to) --> " + var routeString = "\(decodedInfo.packet.to) --> " for node in routingMessage.route { routeString += "\(node) --> " } routeString += "\(decodedInfo.packet.from)" - MeshLogger.log(routeString) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.traceroute.received.route %@", + comment: "Trace Route request returned: %@"), routeString) + MeshLogger.log("🪧 \(logString)") } } case .UNRECOGNIZED(_): diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index fbf26ac9..eea28e81 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1020,7 +1020,8 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { func positionPacket (packet: MeshPacket, context: NSManagedObjectContext) { - MeshLogger.log("📍 Position Packet received from node: \(packet.from)") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.position.received %@", comment: "Position Packet received from node: %@"), String(packet.from)) + MeshLogger.log("📍 \(logString)") let fetchNodePositionRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) @@ -1078,8 +1079,11 @@ func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSMana if let routingMessage = try? Routing(serializedData: packet.decoded.payload) { let routingError = RoutingError(rawValue: routingMessage.errorReason.rawValue) + + let routingErrorString = routingError?.display ?? NSLocalizedString("unknown", comment: "") + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.routing.message %@ %@", comment: "Routing received for RequestID: %@ Ack Status: %@"), String(packet.decoded.requestID), routingErrorString) + MeshLogger.log("🕸️ \(logString)") - MeshLogger.log("🕸️ Routing received for RequestID: \(packet.decoded.requestID) Ack Status: \(routingError?.display ?? NSLocalizedString("unknown", comment: ""))") let fetchMessageRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MessageEntity") fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID)) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 0fd8411d..07a91d39 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -149,7 +149,9 @@ "mesh.log.network.config %@"="Network config received: %@"; "mesh.log.nodeinfo.received %@"="Node info received for: %@"; "mesh.log.position.config %@"="Positon config received: %@"; +"mesh.log.position.received %@"="Position Packet received from node: %@"; "mesh.log.rangetest.config %@"="Range Test module config received: %@"; +"mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@"; "mesh.log.serial.config %@"="Serial module config received: %@"; "mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; "mesh.log.telemetry.config %@"="Telemetry module config received: %@"; @@ -158,6 +160,8 @@ "mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; "mesh.log.textmessage.sent %@ %@ %@"="Sent message %@ from %@ to %@"; "mesh.log.traceroute.sent %@"="Sent a Trace Route Request to node: %@"; +"mesh.log.traceroute.received.direct %@"="Trace Route request sent to node: %@ was recieived directly."; +"mesh.log.traceroute.received.route %@"="Trace Route request returned: %@"; "mesh.log.wantconfig %@"="Issuing Want Config to %@"; "mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@"; "message"="Nachricht"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index fef9802c..a2c58ba0 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -149,7 +149,9 @@ "mesh.log.network.config %@"="Network config received: %@"; "mesh.log.nodeinfo.received %@"="Node info received for: %@"; "mesh.log.position.config %@"="Positon config received: %@"; +"mesh.log.position.received %@"="Position Packet received from node: %@"; "mesh.log.rangetest.config %@"="Range Test module config received: %@"; +"mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@"; "mesh.log.serial.config %@"="Serial module config received: %@"; "mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; "mesh.log.telemetry.config %@"="Telemetry module config received: %@"; @@ -157,6 +159,8 @@ "mesh.log.textmessage.received"="Message received from the text message app."; "mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; "mesh.log.textmessage.sent %@ %@ %@"="Sent message %@ from %@ to %@"; +"mesh.log.traceroute.received.direct %@"="Trace Route request sent to node: %@ was recieived directly."; +"mesh.log.traceroute.received.route %@"="Trace Route request returned: %@"; "mesh.log.traceroute.sent %@"="Sent a Trace Route Request to node: %@"; "mesh.log.wantconfig %@"="Issuing Want Config to %@"; "mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@"; From e95ccee52eabe0c4edbcbf7ec780bcce0358e0b9 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 2 Jan 2023 01:27:57 -0800 Subject: [PATCH 25/40] A couple more localized strings, filter out default location for distance text on node list --- Meshtastic/Helpers/BLEManager.swift | 2 +- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- Meshtastic/Views/Helpers/ConnectedDevice.swift | 2 +- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 12 ++++++------ Meshtastic/Views/Nodes/NodeList.swift | 2 +- Meshtastic/Views/Settings/Settings.swift | 7 ------- de.lproj/Localizable.strings | 4 ++++ en.lproj/Localizable.strings | 4 ++++ 8 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 0369d2a4..e1cbaec9 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -93,7 +93,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { self.timeoutTimerCount += 1 self.lastConnectionError = "" - if timeoutTimerCount == 2 { + if timeoutTimerCount == 10 { if connectedPeripheral != nil { self.centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) } diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 87655736..7094441d 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -194,7 +194,7 @@ struct Connect: View { } } else { - Text("Bluetooth: OFF") + Text("bluetooth.off") .foregroundColor(.red) .font(.title) } diff --git a/Meshtastic/Views/Helpers/ConnectedDevice.swift b/Meshtastic/Views/Helpers/ConnectedDevice.swift index e3552229..ce0f5e66 100644 --- a/Meshtastic/Views/Helpers/ConnectedDevice.swift +++ b/Meshtastic/Views/Helpers/ConnectedDevice.swift @@ -30,7 +30,7 @@ struct ConnectedDevice: View { } } else { - Text("Bluetooth Off").font(.subheadline).foregroundColor(.red) + Text("bluetooth.off").font(.subheadline).foregroundColor(.red) } } } diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index a6ac960b..0925335d 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -134,7 +134,7 @@ struct DeviceMetricsLog: View { Button(role: .destructive) { isPresentingClearLogConfirm = true } label: { - Label("Clear Log", systemImage: "trash.fill") + Label("clear.log", systemImage: "trash.fill") } .buttonStyle(.bordered) .buttonBorderShape(.capsule) @@ -145,7 +145,7 @@ struct DeviceMetricsLog: View { isPresented: $isPresentingClearLogConfirm, titleVisibility: .visible ) { - Button("Delete all device metrics?", role: .destructive) { + Button("device.metrics.delete", role: .destructive) { if clearTelemetry(destNum: node.num, metricsType: 0, context: context) { print("Cleared Device Metrics for \(node.num)") } else { @@ -164,7 +164,7 @@ struct DeviceMetricsLog: View { .controlSize(.large) .padding() } - .navigationTitle("Device Metrics Log") + .navigationTitle("device.metrics.log") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: ZStack { @@ -177,14 +177,14 @@ struct DeviceMetricsLog: View { isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), contentType: .commaSeparatedText, - defaultFilename: String("\(node.user!.longName ?? "Node") Device Telemetry Log"), + defaultFilename: String("\(node.user!.longName ?? "Node") \(NSLocalizedString("device.metrics.log", comment: "Device Metrics Log"))"), onCompletion: { result in if case .success = result { - print("Device Telemetry log download succeeded.") + print("Device metrics log download succeeded.") self.isExporting = false } else { - print("Device Telemetry log download failed: \(result).") + print("Device metrics log download failed: \(result).") } } ) diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index d8c16f25..1925c767 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -53,7 +53,7 @@ struct NodeList: View { HStack(alignment: .bottom) { let lastPostion = node.positions!.reversed()[0] as! PositionEntity let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) - if lastPostion.coordinate != nil { + if lastPostion.coordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { let nodeCoord = CLLocation(latitude: lastPostion.coordinate!.latitude, longitude: lastPostion.coordinate!.longitude) let metersAway = nodeCoord.distance(from: myCoord) Image(systemName: "lines.measurement.horizontal") diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index b48411ea..d4728fc1 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -177,15 +177,10 @@ struct Settings: View { Text("admin.log") } } - Section(header: Text("about")) { - NavigationLink { - AboutMeshtastic() - } label: { - Image(systemName: "questionmark.app") .symbolRenderingMode(.hierarchical) @@ -194,10 +189,8 @@ struct Settings: View { } } .onAppear { - self.bleManager.context = context self.bleManager.userSettings = userSettings - } .listStyle(GroupedListStyle()) .navigationTitle("settings") diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 07a91d39..e96c869b 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -25,6 +25,7 @@ "ble.errorcode.14 %@"="%@ This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; "ble.errorcode.pin %@"="%@ Please try connecting again and check the PIN carefully."; "bluetooth"="Bluetooth"; +"bluetooth.off"="Bluetooth is off"; "bluetooth.config"="Bluetooth Konfiguration"; "bluetooth.mode.randompin"="Zufällige PIN"; "bluetooth.mode.fixedpin"="Feste PIN"; @@ -45,6 +46,7 @@ "channel.utilization"="Channel Utilization"; "channels"="Kanäle"; "clear.app.data"="App Daten löschen"; +"clear.log"="Clear Log"; "close"="Close"; "config.save.confirm"="After config values save the node will reboot."; "connected.radio"="Verbundenes Gerät"; @@ -58,6 +60,8 @@ "delete"="Löschen"; "device"="Gerät"; "device.config"="Gerätekonfiguration"; +"device.metrics.delete"="Delete all device metrics?"; +"device.metrics.log"="Device Metrics Log"; "device.role.client"="Client (Standard) - Mit App verbundener Client."; "device.role.clientmute"="Client Leise - Das selbe wie Client, außer das die Pakete nicht über diesen Node weitergeleitet werden. Nimmt nicht am Mesh-Routing teil."; "device.role.router"="Router - Mesh Pakete werden bevorzugt über diesen Node gerouted. Dieser Node wird nicht von einer Client App benutzt. WLAN, Bluetooth und Display sind aus."; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index a2c58ba0..26240362 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -25,6 +25,7 @@ "ble.errorcode.14 %@"="%@ This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; "ble.errorcode.pin %@"="%@ Please try connecting again and check the PIN carefully."; "bluetooth"="Bluetooth"; +"bluetooth.off"="Bluetooth is off"; "bluetooth.config"="Bluetooth Config"; "bluetooth.mode.randompin"="Random PIN"; "bluetooth.mode.fixedpin"="Fixed PIN"; @@ -45,6 +46,7 @@ "channel.utilization"="Channel Utilization"; "channels"="Channels"; "clear.app.data"="Clear App Data"; +"clear.log"="Clear Log"; "close"="Close"; "config.save.confirm"="After config values save the node will reboot."; "connected.radio"="Connected Radio"; @@ -58,6 +60,8 @@ "delete"="Delete"; "device"="Device"; "device.config"="Device Config"; +"device.metrics.delete"="Delete all device metrics?"; +"device.metrics.log"="Device Metrics Log"; "device.role.client"="Client (default) - App connected client."; "device.role.clientmute"="Client Mute - Same as a client except packets will not hop over this node, does not contribute to routing packets for mesh."; "device.role.router"="Router - Mesh packets will prefer to be routed over this node. This node will not be used by client apps. The wifi/ble radios and the oled screen will be put to sleep."; From 12fdddd5656bdba9d813af19bef17f22c45be7da Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 2 Jan 2023 20:11:58 -0800 Subject: [PATCH 26/40] Fix channel bug --- Meshtastic/Helpers/MeshPackets.swift | 4 ++-- .../Views/Settings/Config/Module/MQTTConfig.swift | 14 +++----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index eea28e81..f4fe1498 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -742,7 +742,7 @@ func channelPacket (channel: Channel, fromNum: Int64, context: NSManagedObjectCo MeshLogger.log("🎛️ \(logString)") let fetchedMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") - fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", channel.index, String(fromNum)) + fetchedMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", fromNum) do { @@ -932,7 +932,7 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), packet.from) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.nodeinfo.received %@", comment: "Node info received for: %@"), String(packet.from)) MeshLogger.log("📟 \(logString)") let fetchNodeInfoAppRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index aa91813d..a56d092b 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -25,7 +25,6 @@ struct MQTTConfig: View { Form { Section(header: Text("options")) { - Toggle(isOn: $enabled) { Label("enabled", systemImage: "dot.radiowaves.right") @@ -43,10 +42,8 @@ struct MQTTConfig: View { Label("JSON Enabled", systemImage: "ellipsis.curlybraces") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - } Section(header: Text("Custom Server")) { - HStack { Label("Address", systemImage: "server.rack") TextField("Server Address", text: $address) @@ -54,16 +51,11 @@ struct MQTTConfig: View { .autocapitalization(.none) .disableAutocorrection(true) .onChange(of: address, perform: { value in - let totalBytes = address.utf8.count - // Only mess with the value if it is too big if totalBytes > 30 { - let firstNBytes = Data(username.utf8.prefix(30)) - if let maxBytesString = String(data: firstNBytes, encoding: String.Encoding.utf8) { - // Set the shortName back to the last place where it was the right size address = maxBytesString } @@ -187,17 +179,17 @@ struct MQTTConfig: View { self.hasChanges = false } .onChange(of: enabled) { newEnabled in - if node != nil && node!.mqttConfig != nil { + if node != nil && node?.mqttConfig != nil { if newEnabled != node!.mqttConfig!.enabled { hasChanges = true } } } .onChange(of: encryptionEnabled) { newEncryptionEnabled in - if node != nil && node!.mqttConfig != nil { + if node != nil && node?.mqttConfig != nil { if newEncryptionEnabled != node!.mqttConfig!.encryptionEnabled { hasChanges = true } } } .onChange(of: jsonEnabled) { newJsonEnabled in - if node != nil && node!.mqttConfig != nil { + if node != nil && node?.mqttConfig != nil { if newJsonEnabled != node!.mqttConfig!.jsonEnabled { hasChanges = true } } } From 4467c7ab9e79ce7a13c9288e8c7644d9040682fe Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 2 Jan 2023 20:53:40 -0800 Subject: [PATCH 27/40] Stop a couple of mesh log items from crashing --- Meshtastic/Helpers/BLEManager.swift | 5 ++--- Meshtastic/Helpers/MeshPackets.swift | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index e1cbaec9..93d38538 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -357,8 +357,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } else { let nodeName = connectedPeripheral!.peripheral.name ?? NSLocalizedString("unknown", comment: NSLocalizedString("unknown", comment: "Unknown")) - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.wantconfig %@", - comment: "Issuing Want Config to %@"), nodeName) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.wantconfig %@", comment: "Issuing Want Config to %@"), nodeName) MeshLogger.log("🛎️ \(logString)") //BLE Characteristics discovered, issue wantConfig var toRadio: ToRadio = ToRadio() @@ -1029,7 +1028,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse) - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.channel.sent %d", comment: "Sent a Channel for: %d Channel Index %d"), connectedPeripheral.num, chan.index) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.channel.sent %@ %d", comment: "Sent a Channel for: %@ Channel Index %d"), String(connectedPeripheral.num), chan.index) MeshLogger.log("🎛️ \(logString)") } } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index f4fe1498..ec5fcca6 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -913,7 +913,6 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje do { try context.save() print("💾 NodeInfo saved for \(nodeInfo.num)") - return fetchedNode[0] } catch { context.rollback() From 866d21246e02a4661b97f68c370e5d2e57096d94 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 2 Jan 2023 21:37:11 -0800 Subject: [PATCH 28/40] Fix a couple more potential crashes --- Meshtastic/Helpers/BLEManager.swift | 2 +- Meshtastic/MeshtasticApp.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 93d38538..a6ef687e 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1052,7 +1052,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { self.connectedPeripheral.peripheral.writeValue(binaryData, for: self.TORADIO_characteristic, type: .withResponse) - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.lora.config.sent %@", comment: "Sent a LoRaConfig for: %@"), connectedPeripheral.num) + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.lora.config.sent %@", comment: "Sent a LoRaConfig for: %@"), String(connectedPeripheral.num)) MeshLogger.log("📻 \(logString)") } return true diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 84ae1e6a..7d9b80cf 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -31,7 +31,7 @@ struct MeshtasticAppleApp: App { print("URL received \(userActivity)") self.incomingUrl = userActivity.webpageURL - if self.incomingUrl!.absoluteString.lowercased().contains("meshtastic.org/e/#") { + if ((self.incomingUrl?.absoluteString.lowercased().contains("meshtastic.org/e/#")) != nil) { if let components = self.incomingUrl?.absoluteString.components(separatedBy: "#") { self.channelSettings = components.last! From 47718f126fbd9bfae5c8cccee64b1b6e6c39e038 Mon Sep 17 00:00:00 2001 From: formtapez Date: Tue, 3 Jan 2023 19:49:19 +0100 Subject: [PATCH 29/40] german translation --- de.lproj/Localizable.strings | 78 ++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index e96c869b..38ccc1ba 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -8,7 +8,7 @@ "about"="Über"; "about.meshtastic"="Über Meshtastic"; "admin"="admin"; -"admin.log"="Admin Message Log"; +"admin.log"="Admin Log"; "ago"="her"; "airtime"="Airtime"; "always.on"="Immer an"; @@ -17,38 +17,38 @@ "ascii.capable"="ASCII fähig"; "available.radios"="Geräte in der Nähe"; "automatic.detection"="Automatische erkennung"; -"battery.level"="Battery Level"; -"battery.level.trend"="Battery Level Trend"; -"ble.name"="BLE Name";"ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."; -"ble.connection.timeout %d %@"="Connection failed after %d attempts to connect to %@. You may need to forget your device under Settings > Bluetooth."; -"ble.errorcode.6 %@"="%@ The app will automatically reconnect to the preferred radio if it come back in range."; -"ble.errorcode.14 %@"="%@ This error usually cannot be fixed without forgetting the device unders Settings > Bluetooth and re-connecting to the radio."; -"ble.errorcode.pin %@"="%@ Please try connecting again and check the PIN carefully."; +"battery.level"="Batterie Ladung"; +"battery.level.trend"="Batterie Ladungstrend"; +"ble.name"="BLE Name";"ble.connection.timeout %d %@"="Verbindung nach %d Versuchen zu %@ fehlgeschlagen. Evtl. hilft es, die Verbindung unter Einstellungen > Bluetooth manuell zu löschen."; +"ble.connection.timeout %d %@"="Verbindung nach %d Versuchen zu %@ fehlgeschlagen. Evtl. hilft es, die Verbindung unter Einstellungen > Bluetooth manuell zu löschen."; +"ble.errorcode.6 %@"="%@ Die App wird automatisch zum präferierten Gerät wiederverbinden, sobald es in Reichweite kommt."; +"ble.errorcode.14 %@"="%@ Dieser fehler kann üblicherweise behoben werden, in dem man unter Einstellungen > Bluetooth die Verbindung manuell löscht und sich erneut mit dem Gerät verbindet."; +"ble.errorcode.pin %@"="%@ Bitte versuche es erneut. achte sorgfältig auf die richtige PIN."; "bluetooth"="Bluetooth"; -"bluetooth.off"="Bluetooth is off"; +"bluetooth.off"="Bluetooth ist aus"; "bluetooth.config"="Bluetooth Konfiguration"; "bluetooth.mode.randompin"="Zufällige PIN"; "bluetooth.mode.fixedpin"="Feste PIN"; "bluetooth.mode.nopin"="Keine PIN (geht einfach)"; -"bluetooth.pairingmode"="Pairing Mode"; -"bluetooth.pin.validation"="BLE Pin must be 6 digits long."; +"bluetooth.pairingmode"="Pairing Modus"; +"bluetooth.pin.validation"="Die Bluetooth Pin muss 6 Stellen lang sein."; "bytes"="Bytes"; "cancel"="Abbrechen"; "canned.messages"="Canned Messages"; "canned.messages.config"="Canned Messages Config"; -"canned.messages.preset.manual"="Manualle Konfiguration"; +"canned.messages.preset.manual"="Manuelle Konfiguration"; "canned.messages.preset.rakrotary"="RAK Drehimpulsgeber Modul"; "canned.messages.preset.cardkb"="M5 Stack Card KB / RAK Tastenfeld"; "channel"="Kanal"; "channel.role.disabled"="Deaktiviert"; "channel.role.primary"="Primär"; "channel.role.secondary"="Sekundär"; -"channel.utilization"="Channel Utilization"; +"channel.utilization"="Kanalbelegung"; "channels"="Kanäle"; "clear.app.data"="App Daten löschen"; -"clear.log"="Clear Log"; -"close"="Close"; -"config.save.confirm"="After config values save the node will reboot."; +"clear.log"="Log löschen"; +"close"="Schließen"; +"config.save.confirm"="Nach dem ändern der Einstellungen wird das Gerät neu starten."; "connected.radio"="Verbundenes Gerät"; "communicating"="Verbinde mit Gerät..."; "connected"="Derzeit verbunden"; @@ -75,14 +75,14 @@ "echo"="Echo"; "email.address"="Email Adresse"; "enabled"="Aktiviert"; -"encrypted"="Encrypted"; +"encrypted"="Verschlüsselt"; "external.notification"="Externe Benachrichtigung"; "external.notification.config"="Einstellungen der externen Benachrichtigung"; "firmware.version"="Firmware Version"; -"firmware.version.unsupported"="Unsupported Firmware Version Detected, unable to connect to device."; +"firmware.version.unsupported"="Nicht unterstützte Firmware Version erkannt. Kann nicht verbinden."; "gas"="Gas"; "gas.resistance"="Gas Resistance"; -"generate.qr.code"="Generate QR Code"; +"generate.qr.code"="QR Code Erzeugen"; "gpsformat.dec"="Dezimalgrad Format"; "gpsformat.dms"="Grad Minuten Sekunden"; "gpsformat.utm"="Universal Transversal Mercator"; @@ -136,36 +136,36 @@ "map"="Mesh Karte"; "map.type"="kartentyp"; "mesh.log"="Mesh Log"; -"mesh.log.bluetooth.config %@"="Bluetooth config received: %@"; +"mesh.log.bluetooth.config %@"="Bluetooth Konfiguration empfangen: %@"; "mesh.log.cannedmessage.config %@"="Canned Message module config received: %@"; "mesh.log.cannedmessages.messages.get %@"="Requested Canned Messages Module Messages for node: %@"; "mesh.log.cannedmessages.messages.received %@"="Canned Messages Messages Received For: %@"; "mesh.log.channel.sent %@ %d"="Sent a Channel for: %@ Channel Index %d"; "mesh.log.channel.received %d %@"="Channel %d received from: %@"; -"mesh.log.device.config %@"="Device config received: %@"; -"mesh.log.display.config %@"="Display config received: %@"; -"mesh.log.devicemetadata %@"="Requesting Device Metadata for %@"; +"mesh.log.device.config %@"="Geräte Konfiguration empfangen: %@"; +"mesh.log.display.config %@"="Display Konfiguration empfangen: %@"; +"mesh.log.devicemetadata %@"="Anforderung der Geräte Metadaten für %@"; "mesh.log.externalnotification.config %@"="External Notifiation module config received: %@"; "mesh.log.lora.config %@"="LoRa config received: %@"; "mesh.log.lora.config.sent %@"="Sent a LoRa.Config for: %@"; "mesh.log.mqtt.config %@"="MQTT module config received: %@"; "mesh.log.myinfo %@"="MyInfo received: %@"; -"mesh.log.network.config %@"="Network config received: %@"; -"mesh.log.nodeinfo.received %@"="Node info received for: %@"; -"mesh.log.position.config %@"="Positon config received: %@"; -"mesh.log.position.received %@"="Position Packet received from node: %@"; -"mesh.log.rangetest.config %@"="Range Test module config received: %@"; -"mesh.log.routing.message %@ %@"="Routing received for RequestID: %@ Ack Status: %@"; -"mesh.log.serial.config %@"="Serial module config received: %@"; +"mesh.log.network.config %@"="Netzwerk onfiguration empfangen: %@"; +"mesh.log.nodeinfo.received %@"="Node info empfangen für: %@"; +"mesh.log.position.config %@"="Positions Konfiguration empfangen: %@"; +"mesh.log.position.received %@"="Positionspaket empfangen von Node: %@"; +"mesh.log.rangetest.config %@"="Range Test Modul konfiguration empfangen: %@"; +"mesh.log.routing.message %@ %@"="Routing empfangen für RequestID: %@ Ack Status: %@"; +"mesh.log.serial.config %@"="Serial Modul Konfiguration empfangen: %@"; "mesh.log.sharelocation %@"="Sent a Position Packet from the Apple device GPS to node: %@"; -"mesh.log.telemetry.config %@"="Telemetry module config received: %@"; -"mesh.log.telemetry.received %@"="Telemetry received for: %@"; -"mesh.log.textmessage.received"="Message received from the text message app."; -"mesh.log.textmessage.send.failed %@"="Message Send Failed, not properly connected to %@"; -"mesh.log.textmessage.sent %@ %@ %@"="Sent message %@ from %@ to %@"; -"mesh.log.traceroute.sent %@"="Sent a Trace Route Request to node: %@"; -"mesh.log.traceroute.received.direct %@"="Trace Route request sent to node: %@ was recieived directly."; -"mesh.log.traceroute.received.route %@"="Trace Route request returned: %@"; +"mesh.log.telemetry.config %@"="Telemetrie Modul Konfiguration empfangen: %@"; +"mesh.log.telemetry.received %@"="Telemetrie empfangen für: %@"; +"mesh.log.textmessage.received"="Nachricht von der Textnachricht-App empfangen."; +"mesh.log.textmessage.send.failed %@"="Nachricht senden fehlgeschlagen. Nicht korrekt verbunden zu %@"; +"mesh.log.textmessage.sent %@ %@ %@"="Sende Nachricht %@ von %@ an %@"; +"mesh.log.traceroute.sent %@"="Sende Traceroute Anforderung zu Mode: %@"; +"mesh.log.traceroute.received.direct %@"="Traceroute Anforderung an node gesendet: %@ wurde direkt empfangen."; +"mesh.log.traceroute.received.route %@"="Traceroute Ergebnis: %@"; "mesh.log.wantconfig %@"="Issuing Want Config to %@"; "mesh.log.waypoint.sent %@"="Sent a Waypoint Packet from: %@"; "message"="Nachricht"; @@ -197,7 +197,7 @@ "range.test"="Entfernungstest"; "range.test.config"="Entfernungstest Konfiguration"; "reboot"="Reboot"; -"reboot.node"="Reboot node?"; +"reboot.node"="Node neustarten?"; "reply"="Antworten"; "received.ack"="Empfangsbestätigung"; "received.ack.real"="Recipient Ack"; From beb09b627c3cfc192bf7c1afc5b110933700f280 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 3 Jan 2023 21:45:10 -0800 Subject: [PATCH 30/40] * Automatic Reconnect Updates * Channel Editor Updates --- Meshtastic/Helpers/BLEManager.swift | 40 +++++++++++--- Meshtastic/Helpers/MeshPackets.swift | 53 ++++++++++--------- Meshtastic/Views/Bluetooth/Connect.swift | 4 +- Meshtastic/Views/Settings/Channels.swift | 17 +++--- .../Config/Module/CannedMessagesConfig.swift | 4 +- 5 files changed, 75 insertions(+), 43 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index a6ef687e..c25dffe7 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -27,6 +27,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { @Published var invalidVersion = false @Published var preferredPeripheral = false @Published var isSwitchedOn: Bool = false + @Published var automaticallyReconnect: Bool = true public var minimumVersion = "1.3.48" public var connectedVersion: String @@ -123,6 +124,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { DispatchQueue.main.async { self.isConnecting = true self.lastConnectionError = "" + self.automaticallyReconnect = true } if connectedPeripheral != nil { print("ℹ️ BLE Disconnecting from: \(connectedPeripheral.name) to connect to \(peripheral.name ?? "Unknown")") @@ -142,9 +144,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { } // Disconnect Connected Peripheral - func disconnectPeripheral() { + func disconnectPeripheral(reconnect: Bool = true) { guard let connectedPeripheral = connectedPeripheral else { return } + automaticallyReconnect = reconnect centralManager?.cancelPeripheralConnection(connectedPeripheral.peripheral) FROMRADIO_characteristic = nil isConnected = false @@ -393,7 +396,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { comment: "Please try connecting again and check the PIN carefully."), e.localizedDescription) print("🚨 \(e.localizedDescription) Please try connecting again and check the PIN carefully.") - self.centralManager?.cancelPeripheralConnection(peripheral) + self.disconnectPeripheral(reconnect: false) } } @@ -566,11 +569,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { do { let fetchedUser = try context?.fetch(fetchBCUserRequest) as! [UserEntity] if fetchedUser.count > 0 { - context?.delete(fetchedUser[0]) print("🗑️ Deleted the All - Broadcast User") } - } catch { print("💥 Error Deleting the All - Broadcast User") } @@ -942,6 +943,32 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { return success } + public func getChannel(channel: Channel, fromUser: UserEntity, toUser: UserEntity) -> Int64 { + + var adminPacket = AdminMessage() + adminPacket.getChannelRequest = UInt32(channel.index + 1) + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(toUser.num) + meshPacket.from = 0 //UInt32(fromUser.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() @@ -956,13 +983,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var dataMessage = DataMessage() dataMessage.payload = try! adminPacket.serializedData() dataMessage.portnum = PortNum.adminApp - + dataMessage.wantResponse = true meshPacket.decoded = dataMessage let messageDescription = "Saved Channel \(channel.index) for \(toUser.longName ?? NSLocalizedString("unknown", comment: "Unknown"))" if sendAdminMessageToRadio(meshPacket: meshPacket, adminDescription: messageDescription, fromUser: fromUser, toUser: toUser) { - return Int64(meshPacket.id) } @@ -1567,7 +1593,7 @@ extension BLEManager: CBCentralManagerDelegate { // Called each time a peripheral is discovered func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { - if timeoutTimerRuns < 2 && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" { + if self.automaticallyReconnect && timeoutTimerRuns < 2 && peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" { self.connectTo(peripheral: peripheral) print("ℹ️ BLE Reconnecting to prefered peripheral: \(peripheral.name ?? "Unknown")") } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index ec5fcca6..fb21e3fc 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -985,34 +985,37 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.received %@", comment: "Canned Messages Messages Received For: %@"), String(packet.from)) - MeshLogger.log("🥫 \(logString)") - + if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { - - let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") - fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - - do { - let fetchedNode = try context.fetch(fetchNodeRequest) as! [NodeInfoEntity] - if fetchedNode.count == 1 { - let messages = String(cmmc.textFormatString()) - .replacingOccurrences(of: "11: ", with: "") - .replacingOccurrences(of: "\"", with: "") - .trimmingCharacters(in: .whitespacesAndNewlines) - fetchedNode[0].cannedMessageConfig?.messages = messages - do { - try context.save() - print("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") - } catch { - context.rollback() - let nsError = error as NSError - print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") + + if !cmmc.messages.isEmpty { + + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.received %@", comment: "Canned Messages Messages Received For: %@"), String(packet.from)) + MeshLogger.log("🥫 \(logString)") + + let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + let fetchedNode = try context.fetch(fetchNodeRequest) as! [NodeInfoEntity] + if fetchedNode.count == 1 { + let messages = String(cmmc.textFormatString()) + .replacingOccurrences(of: "11: ", with: "") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + fetchedNode[0].cannedMessageConfig?.messages = messages + do { + try context.save() + print("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") + } } + } catch { + print("💥 Error Deserializing POSITION_APP packet.") } - } catch { - print("💥 Error Deserializing POSITION_APP packet.") } } } diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 7094441d..817c19d5 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -88,7 +88,7 @@ struct Connect: View { Button(role: .destructive) { if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { - bleManager.disconnectPeripheral() + bleManager.disconnectPeripheral(reconnect: false) isPreferredRadio = false } } label: { @@ -287,6 +287,6 @@ struct Connect: View { }) } func didDismissSheet() { - bleManager.disconnectPeripheral() + bleManager.disconnectPeripheral(reconnect: false) } } diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 4a6bb4a5..7d115fc6 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -240,22 +240,25 @@ struct Channels: View { var channel = Channel() channel.index = channelIndex - channel.settings.id = UInt32(channelIndex) - channel.settings.name = channelName - channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary - channel.settings.uplinkEnabled = uplink - channel.settings.downlinkEnabled = downlink + if channel.role != Channel.Role.disabled { + channel.settings.id = UInt32(channelIndex) + channel.settings.name = channelName + channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() + channel.settings.uplinkEnabled = uplink + channel.settings.downlinkEnabled = downlink + } let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true - // for now just disable the button after a successful save + // for now just disable the button after a successful save. + // Would rather send a getChannel but I can't seem to tell what admin message it is + bleManager.sendWantConfig() channelName = "" hasChanges = false isPresentingEditView = false - bleManager.disconnectPeripheral() } } } diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 01d51a62..c7966528 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -74,7 +74,7 @@ struct CannedMessagesConfig: View { HStack { Label("Messages", systemImage: "message.fill") - TextField("Messages seperate with |", text: $messages) + TextField("Messages seperate with |", text: $messages, axis: .vertical) .foregroundColor(.gray) .autocapitalization(.none) .disableAutocorrection(true) @@ -256,7 +256,7 @@ struct CannedMessagesConfig: View { // for now just disable the button after a successful save hasMessagesChanges = false if !hasChanges { - bleManager.sendReboot(fromUser: node!.user!, toUser: node!.user!) + bleManager.sendWantConfig() goBack() } } From 8c19fa0fc4e83339e8dcb2b66df00b10acd44837 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 3 Jan 2023 23:05:35 -0800 Subject: [PATCH 31/40] Protect against weird channel index problems --- Meshtastic/Views/Settings/About.swift | 2 +- Meshtastic/Views/Settings/Channels.swift | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Settings/About.swift b/Meshtastic/Views/Settings/About.swift index 32745f11..43e67809 100644 --- a/Meshtastic/Views/Settings/About.swift +++ b/Meshtastic/Views/Settings/About.swift @@ -46,7 +46,7 @@ struct AboutMeshtastic: View { Link("Documentation", destination: URL(string: "https://meshtastic.org/docs/getting-started")!) .font(.title2) } - Text("Meshtastic Copyright(c) Meshtastic LLC") + Text("Meshtastic® Copyright Meshtastic LLC") .font(.caption) } } diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 7d115fc6..b335064e 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -91,7 +91,7 @@ struct Channels: View { Button { let key = generateChannelKey(size: 32) channelName = "" - channelIndex = Int32(node!.myInfo!.channels!.array.count) + channelIndex = Int32((node!.myInfo!.channels!.array.count) - 1) channelRole = 2 channelKey = key uplink = false @@ -247,6 +247,20 @@ struct Channels: View { channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() channel.settings.uplinkEnabled = uplink channel.settings.downlinkEnabled = downlink + + } else { + if channelIndex <= node!.myInfo!.channels?.count ?? 0 { + let channelEntity = node!.myInfo!.channels?[Int(channelIndex)] as! ChannelEntity + context.delete(channelEntity) + do { + try context.save() + print("💾 Deleted Channel: \(channel.settings.name)") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)") + } + } } let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) From a7a1c734ab37d88e25e54dcf52d1776eac3e3d4d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 3 Jan 2023 23:20:33 -0800 Subject: [PATCH 32/40] Show advanced external notification settings by default --- .../Config/Module/ExternalNotificationConfig.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index 15233435..a8df56dc 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -54,8 +54,12 @@ struct ExternalNotificationConfig: View { Text("Use a PWM output (like the RAK Buzzer) for tunes instead of an on/off output. This will ignore the output, output duration and active settings and use the device config buzzer GPIO option instead.") .font(.caption) } - if !usePWM { - Section(header: Text("Primary GPIO")) { + Section(header: Text("Advanced GPIO Options")) { + Section(header: Text("Primary GPIO") + .font(.caption) + .foregroundColor(.gray) + .textCase(.uppercase)) + { Toggle(isOn: $active) { Label("Active", systemImage: "togglepower") } @@ -90,7 +94,11 @@ struct ExternalNotificationConfig: View { .font(.caption) } - Section(header: Text("Optional GPIO")) { + Section(header: Text("Optional GPIO") + .font(.caption) + .foregroundColor(.gray) + .textCase(.uppercase)) + { Toggle(isOn: $alertBellBuzzer) { Label("Alert GPIO buzzer when receiving a bell", systemImage: "bell") } From 699e87020612f87c5773fa5fbd257fe2b3bc5d50 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 3 Jan 2023 23:24:35 -0800 Subject: [PATCH 33/40] Stupid index --- Meshtastic/Views/Settings/Channels.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index b335064e..504b0087 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -91,7 +91,7 @@ struct Channels: View { Button { let key = generateChannelKey(size: 32) channelName = "" - channelIndex = Int32((node!.myInfo!.channels!.array.count) - 1) + channelIndex = Int32(node!.myInfo!.channels!.array.count) channelRole = 2 channelKey = key uplink = false From 21f96c334f9f843aeeb154bd8cb207f1f026b571 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 4 Jan 2023 10:05:08 -0800 Subject: [PATCH 34/40] Remove confirmation from save channel as it seems to cause issues with the sheet --- Meshtastic/Views/Settings/Channels.swift | 88 +++++++++++------------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index 504b0087..b3cdcc93 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -193,9 +193,9 @@ struct Channels: View { Toggle("Downlink Enabled", isOn: $downlink) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } - .onSubmit { + //.onSubmit { //validate(name: channelName) - } + //} .onChange(of: channelName) { newName in hasChanges = true } @@ -222,7 +222,44 @@ struct Channels: View { } HStack { Button { - isPresentingSaveConfirm = true + var channel = Channel() + channel.index = channelIndex + channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary + if channel.role != Channel.Role.disabled { + channel.settings.id = UInt32(channelIndex) + channel.settings.name = channelName + channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() + channel.settings.uplinkEnabled = uplink + channel.settings.downlinkEnabled = downlink + + } else { + if channelIndex <= node!.myInfo!.channels?.count ?? 0 { + let channelEntity = node!.myInfo!.channels?[Int(channelIndex)] as! ChannelEntity + context.delete(channelEntity) + do { + try context.save() + print("💾 Deleted Channel: \(channel.settings.name)") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)") + } + } + } + + let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) + + if adminMessageId > 0 { + + // Should show a saved successfully alert once I know that to be true + // for now just disable the button after a successful save. + + self.isPresentingEditView = false + channelName = "" + hasChanges = false + // Would rather send a getChannel but I can't seem to tell what admin message it is + bleManager.sendWantConfig() + } } label: { Label("save", systemImage: "square.and.arrow.down") } @@ -231,51 +268,6 @@ struct Channels: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding(.bottom) - .confirmationDialog( - "are.you.sure", - isPresented: $isPresentingSaveConfirm, - titleVisibility: .visible - ) { - Button("Save Channel \(channelIndex) to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - - var channel = Channel() - channel.index = channelIndex - channel.role = ChannelRoles(rawValue: channelRole)?.protoEnumValue() ?? .secondary - if channel.role != Channel.Role.disabled { - channel.settings.id = UInt32(channelIndex) - channel.settings.name = channelName - channel.settings.psk = Data(base64Encoded: channelKey) ?? Data() - channel.settings.uplinkEnabled = uplink - channel.settings.downlinkEnabled = downlink - - } else { - if channelIndex <= node!.myInfo!.channels?.count ?? 0 { - let channelEntity = node!.myInfo!.channels?[Int(channelIndex)] as! ChannelEntity - context.delete(channelEntity) - do { - try context.save() - print("💾 Deleted Channel: \(channel.settings.name)") - } catch { - context.rollback() - let nsError = error as NSError - print("💥 Unresolved Core Data error in the channel editor. Error: \(nsError)") - } - } - } - - let adminMessageId = bleManager.saveChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) - - if adminMessageId > 0 { - // Should show a saved successfully alert once I know that to be true - // for now just disable the button after a successful save. - // Would rather send a getChannel but I can't seem to tell what admin message it is - bleManager.sendWantConfig() - channelName = "" - hasChanges = false - isPresentingEditView = false - } - } - } #if targetEnvironment(macCatalyst) Button { isPresentingEditView = false From 630986487c7b663d7849d7fd38dea48790776697 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 6 Jan 2023 00:56:44 -0800 Subject: [PATCH 35/40] Handle some nulls, properly identify and serialized assorted admin packets --- Meshtastic/Helpers/MeshPackets.swift | 61 ++++++++++++--------- Meshtastic/Persistence/UpdateCoreData.swift | 10 +--- Meshtastic/Views/Bluetooth/Connect.swift | 2 +- Meshtastic/Views/Messages/Contacts.swift | 13 ++++- Meshtastic/Views/Settings/Channels.swift | 2 +- 5 files changed, 51 insertions(+), 37 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index fb21e3fc..c5fe49b7 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -985,39 +985,50 @@ func nodeInfoAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { + + if let adminMessage = try? AdminMessage(serializedData: packet.decoded.payload) { + + if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getCannedMessageModuleMessagesResponse(adminMessage.getCannedMessageModuleMessagesResponse) { - if !cmmc.messages.isEmpty { - - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.received %@", comment: "Canned Messages Messages Received For: %@"), String(packet.from)) - MeshLogger.log("🥫 \(logString)") - - let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") - fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - - do { - let fetchedNode = try context.fetch(fetchNodeRequest) as! [NodeInfoEntity] - if fetchedNode.count == 1 { - let messages = String(cmmc.textFormatString()) - .replacingOccurrences(of: "11: ", with: "") - .replacingOccurrences(of: "\"", with: "") - .trimmingCharacters(in: .whitespacesAndNewlines) - fetchedNode[0].cannedMessageConfig?.messages = messages + if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { + + if !cmmc.messages.isEmpty { + + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.cannedmessages.messages.received %@", comment: "Canned Messages Messages Received For: %@"), String(packet.from)) + MeshLogger.log("🥫 \(logString)") + + let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + do { - try context.save() - print("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") + let fetchedNode = try context.fetch(fetchNodeRequest) as! [NodeInfoEntity] + if fetchedNode.count == 1 { + let messages = String(cmmc.textFormatString()) + .replacingOccurrences(of: "11: ", with: "") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + fetchedNode[0].cannedMessageConfig?.messages = messages + do { + try context.save() + print("💾 Updated Canned Messages Messages For: \(fetchedNode[0].num)") + } catch { + context.rollback() + let nsError = error as NSError + print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") + } + } } catch { - context.rollback() - let nsError = error as NSError - print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)") + print("💥 Error Deserializing ADMIN_APP packet.") } } - } catch { - print("💥 Error Deserializing POSITION_APP packet.") } } + + else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) { + print("I am a channel response") + } } + } func positionPacket (packet: MeshPacket, context: NSManagedObjectContext) { diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index bd752922..40bc81b9 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -61,11 +61,8 @@ public func clearTelemetry(destNum: Int64, metricsType: Int32, context: NSManage } public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObjectContext) { - let fetchChannelMessagesRequest = NSFetchRequest(entityName: "MessageEntity") - fetchChannelMessagesRequest.predicate = NSPredicate(format: "channel == %i AND toUser == nil AND admin == false", Int32(channel.id)) - fetchChannelMessagesRequest.includesPropertyValues = false do { - let objects = try context.fetch(fetchChannelMessagesRequest) as! [NSManagedObject] + let objects = channel.allPrivateMessages// try context.fetch(fetchChannelMessagesRequest) as! [NSManagedObject] for object in objects { context.delete(object) } @@ -77,11 +74,8 @@ public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObje public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext) { - let fetchUserMessagesRequest = NSFetchRequest(entityName: "MessageEntity") - fetchUserMessagesRequest.predicate = NSPredicate(format: "((toUser.num == %lld) OR (fromUser.num == %lld)) AND toUser != nil AND fromUser != nil AND admin == false", Int64(user.num), Int64(user.num)) - fetchUserMessagesRequest.includesPropertyValues = false do { - let objects = try context.fetch(fetchUserMessagesRequest) as! [NSManagedObject] + let objects = user.messageList//try context.fetch(fetchUserMessagesRequest) as! [NSManagedObject] for object in objects { context.delete(object) } diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 817c19d5..d9e29e1b 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -210,7 +210,7 @@ struct Connect: View { Button(role: .destructive, action: { if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected { - bleManager.disconnectPeripheral() + bleManager.disconnectPeripheral(reconnect: false) isPreferredRadio = false } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index 06abcafa..d53c365f 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -126,8 +126,17 @@ struct Contacts: View { ) { Button(role: .destructive) { - deleteChannelMessages(channel: channel, context: context) - context.refresh(node!.myInfo!, mergeChanges: true) + do { + for message in channel.allPrivateMessages { + context.delete(message) + } + try context.save() + context.refresh(node!.myInfo!, mergeChanges: true) + } catch let error as NSError { + print("Error: \(error.localizedDescription)") + } + //deleteChannelMessages(channel: channel, context: context) + } label: { Text("delete") } diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index b3cdcc93..4dd5c845 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -86,7 +86,7 @@ struct Channels: View { } } } - if node?.myInfo?.channels?.array.count ?? 0 < 8 { + if node?.myInfo?.channels?.array.count ?? 0 < 8 && node != nil { Button { let key = generateChannelKey(size: 32) From 5b56125628389d947bf3678c3fc04f8a061fb1c2 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 8 Jan 2023 18:36:35 -0800 Subject: [PATCH 36/40] Channel editor updates --- Meshtastic/Helpers/BLEManager.swift | 1 - Meshtastic/Helpers/MeshPackets.swift | 6 +++++- Meshtastic/Views/Settings/Channels.swift | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index c25dffe7..d51723b0 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -978,7 +978,6 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { meshPacket.from = 0 //UInt32(fromUser.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Date: Mon, 9 Jan 2023 16:02:43 -0800 Subject: [PATCH 37/40] Fix delete a messages from contacts, use getchannel instead of wantConfig after saving a channel --- Meshtastic/Helpers/MeshPackets.swift | 5 +---- Meshtastic/Views/Messages/Contacts.swift | 25 +++++++++--------------- Meshtastic/Views/Settings/Channels.swift | 4 ++-- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 01cda76e..d07bb2ba 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1026,10 +1026,7 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { else if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getChannelResponse(adminMessage.getChannelResponse) { - if let channel = try? Channel(serializedData: packet.decoded.payload) { - print("got a channel over the admin channel") - //channelPacket(channel: channel, fromNum: Int64(packet.from), context: context) - } + channelPacket(channel: adminMessage.getChannelResponse, fromNum: Int64(packet.from), context: context) } } diff --git a/Meshtastic/Views/Messages/Contacts.swift b/Meshtastic/Views/Messages/Contacts.swift index d53c365f..241a694e 100644 --- a/Meshtastic/Views/Messages/Contacts.swift +++ b/Meshtastic/Views/Messages/Contacts.swift @@ -20,7 +20,8 @@ struct Contacts: View { private var users: FetchedResults @State var node: NodeInfoEntity? = nil - @State private var selection: UserEntity? = nil // Nothing selected by default. + @State private var userSelection: UserEntity? = nil // Nothing selected by default. + @State private var channelSelection: ChannelEntity? = nil // Nothing selected by default. @State private var isPresentingDeleteChannelMessagesConfirm: Bool = false @State private var isPresentingDeleteUserMessagesConfirm: Bool = false @State private var isPresentingTraceRouteSentAlert = false @@ -113,6 +114,7 @@ struct Contacts: View { if channel.allPrivateMessages.count > 0 { Button(role: .destructive) { isPresentingDeleteChannelMessagesConfirm = true + channelSelection = channel } label: { Label("Delete Messages", systemImage: "trash") } @@ -121,22 +123,12 @@ struct Contacts: View { .confirmationDialog( "This conversation will be deleted.", isPresented: $isPresentingDeleteChannelMessagesConfirm, - titleVisibility: .visible ) { - Button(role: .destructive) { - do { - for message in channel.allPrivateMessages { - context.delete(message) - } - try context.save() - context.refresh(node!.myInfo!, mergeChanges: true) - } catch let error as NSError { - print("Error: \(error.localizedDescription)") - } - //deleteChannelMessages(channel: channel, context: context) - + deleteChannelMessages(channel: channelSelection!, context: context) + context.refresh(node!.myInfo!, mergeChanges: true) + channelSelection = nil } label: { Text("delete") } @@ -217,6 +209,7 @@ struct Contacts: View { if user.messageList.count > 0 { Button(role: .destructive) { isPresentingDeleteUserMessagesConfirm = true + userSelection = user } label: { Label("Delete Messages", systemImage: "trash") } @@ -238,7 +231,7 @@ struct Contacts: View { titleVisibility: .visible ) { Button(role: .destructive) { - deleteUserMessages(user: user, context: context) + deleteUserMessages(user: userSelection!, context: context) context.refresh(node!.user!, mergeChanges: true) } label: { Text("delete") @@ -283,7 +276,7 @@ struct Contacts: View { } } detail: { - if let user = selection { + if let user = userSelection { UserMessageList(user:user) } else { diff --git a/Meshtastic/Views/Settings/Channels.swift b/Meshtastic/Views/Settings/Channels.swift index b4109f99..52a4d8ec 100644 --- a/Meshtastic/Views/Settings/Channels.swift +++ b/Meshtastic/Views/Settings/Channels.swift @@ -258,8 +258,8 @@ struct Channels: View { channelName = "" hasChanges = false // Would rather send a getChannel but I can't seem serialize it properly yet - //bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) - bleManager.sendWantConfig() + bleManager.getChannel(channel: channel, fromUser: node!.user!, toUser: node!.user!) + //bleManager.sendWantConfig() } } label: { Label("save", systemImage: "square.and.arrow.down") From 8b8108c5a34b9990eb8c22c67a3583fba91a2c95 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 9 Jan 2023 18:01:27 -0800 Subject: [PATCH 38/40] Update protobufs Don't allow leading zero for BLE fixed pin Comment out ethMode --- Meshtastic/Enums/EthernetModes.swift | 6 +- Meshtastic/Protobufs/admin.pb.swift | 98 +++++++++++++++ Meshtastic/Protobufs/config.pb.swift | 108 +++++++++++++++-- Meshtastic/Protobufs/localonly.pb.swift | 19 +++ Meshtastic/Protobufs/mesh.pb.swift | 114 ++++++++++++++++++ Meshtastic/Protobufs/module_config.pb.swift | 84 +++++++++++++ .../Settings/Config/BluetoothConfig.swift | 4 + .../Views/Settings/Config/NetworkConfig.swift | 2 +- 8 files changed, 420 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Enums/EthernetModes.swift b/Meshtastic/Enums/EthernetModes.swift index 2297cd04..352586bd 100644 --- a/Meshtastic/Enums/EthernetModes.swift +++ b/Meshtastic/Enums/EthernetModes.swift @@ -24,14 +24,14 @@ enum EthernetMode: Int, CaseIterable, Identifiable { } } } - func protoEnumValue() -> Config.NetworkConfig.EthMode { + func protoEnumValue() -> Config.NetworkConfig.AddressMode { switch self { case .dhcp: - return Config.NetworkConfig.EthMode.dhcp + return Config.NetworkConfig.AddressMode.dhcp case .staticip: - return Config.NetworkConfig.EthMode.static + return Config.NetworkConfig.AddressMode.static } } } diff --git a/Meshtastic/Protobufs/admin.pb.swift b/Meshtastic/Protobufs/admin.pb.swift index 21b5f2a0..ec6d69a8 100644 --- a/Meshtastic/Protobufs/admin.pb.swift +++ b/Meshtastic/Protobufs/admin.pb.swift @@ -154,6 +154,26 @@ struct AdminMessage { set {payloadVariant = .getDeviceMetadataResponse(newValue)} } + /// + /// Get the Ringtone in the response to this message. + var getRingtoneRequest: Bool { + get { + if case .getRingtoneRequest(let v)? = payloadVariant {return v} + return false + } + set {payloadVariant = .getRingtoneRequest(newValue)} + } + + /// + /// Get the Ringtone in the response to this message. + var getRingtoneResponse: String { + get { + if case .getRingtoneResponse(let v)? = payloadVariant {return v} + return String() + } + set {payloadVariant = .getRingtoneResponse(newValue)} + } + /// /// Set the owner for this node var setOwner: User { @@ -208,6 +228,16 @@ struct AdminMessage { set {payloadVariant = .setCannedMessageModuleMessages(newValue)} } + /// + /// Set the ringtone for ExternalNotification. + var setRingtoneMessage: String { + get { + if case .setRingtoneMessage(let v)? = payloadVariant {return v} + return String() + } + set {payloadVariant = .setRingtoneMessage(newValue)} + } + /// /// Begins an edit transaction for config, module config, owner, and channel settings changes /// This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) @@ -357,6 +387,12 @@ struct AdminMessage { /// Device metadata response case getDeviceMetadataResponse(DeviceMetadata) /// + /// Get the Ringtone in the response to this message. + case getRingtoneRequest(Bool) + /// + /// Get the Ringtone in the response to this message. + case getRingtoneResponse(String) + /// /// Set the owner for this node case setOwner(User) /// @@ -376,6 +412,9 @@ struct AdminMessage { /// Set the Canned Message Module messages text. case setCannedMessageModuleMessages(String) /// + /// Set the ringtone for ExternalNotification. + case setRingtoneMessage(String) + /// /// Begins an edit transaction for config, module config, owner, and channel settings changes /// This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) case beginEditSettings(Bool) @@ -466,6 +505,14 @@ struct AdminMessage { guard case .getDeviceMetadataResponse(let l) = lhs, case .getDeviceMetadataResponse(let r) = rhs else { preconditionFailure() } return l == r }() + case (.getRingtoneRequest, .getRingtoneRequest): return { + guard case .getRingtoneRequest(let l) = lhs, case .getRingtoneRequest(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.getRingtoneResponse, .getRingtoneResponse): return { + guard case .getRingtoneResponse(let l) = lhs, case .getRingtoneResponse(let r) = rhs else { preconditionFailure() } + return l == r + }() case (.setOwner, .setOwner): return { guard case .setOwner(let l) = lhs, case .setOwner(let r) = rhs else { preconditionFailure() } return l == r @@ -486,6 +533,10 @@ struct AdminMessage { guard case .setCannedMessageModuleMessages(let l) = lhs, case .setCannedMessageModuleMessages(let r) = rhs else { preconditionFailure() } return l == r }() + case (.setRingtoneMessage, .setRingtoneMessage): return { + guard case .setRingtoneMessage(let l) = lhs, case .setRingtoneMessage(let r) = rhs else { preconditionFailure() } + return l == r + }() case (.beginEditSettings, .beginEditSettings): return { guard case .beginEditSettings(let l) = lhs, case .beginEditSettings(let r) = rhs else { preconditionFailure() } return l == r @@ -634,6 +685,10 @@ struct AdminMessage { /// /// TODO: REPLACE case audioConfig // = 7 + + /// + /// TODO: REPLACE + case remotehardwareConfig // = 8 case UNRECOGNIZED(Int) init() { @@ -650,6 +705,7 @@ struct AdminMessage { case 5: self = .telemetryConfig case 6: self = .cannedmsgConfig case 7: self = .audioConfig + case 8: self = .remotehardwareConfig default: self = .UNRECOGNIZED(rawValue) } } @@ -664,6 +720,7 @@ struct AdminMessage { case .telemetryConfig: return 5 case .cannedmsgConfig: return 6 case .audioConfig: return 7 + case .remotehardwareConfig: return 8 case .UNRECOGNIZED(let i): return i } } @@ -699,6 +756,7 @@ extension AdminMessage.ModuleConfigType: CaseIterable { .telemetryConfig, .cannedmsgConfig, .audioConfig, + .remotehardwareConfig, ] } @@ -728,11 +786,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 11: .standard(proto: "get_canned_message_module_messages_response"), 12: .standard(proto: "get_device_metadata_request"), 13: .standard(proto: "get_device_metadata_response"), + 14: .standard(proto: "get_ringtone_request"), + 15: .standard(proto: "get_ringtone_response"), 32: .standard(proto: "set_owner"), 33: .standard(proto: "set_channel"), 34: .standard(proto: "set_config"), 35: .standard(proto: "set_module_config"), 36: .standard(proto: "set_canned_message_module_messages"), + 37: .standard(proto: "set_ringtone_message"), 64: .standard(proto: "begin_edit_settings"), 65: .standard(proto: "commit_edit_settings"), 66: .standard(proto: "confirm_set_channel"), @@ -872,6 +933,22 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .getDeviceMetadataResponse(v) } }() + case 14: try { + var v: Bool? + try decoder.decodeSingularBoolField(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .getRingtoneRequest(v) + } + }() + case 15: try { + var v: String? + try decoder.decodeSingularStringField(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .getRingtoneResponse(v) + } + }() case 32: try { var v: User? var hadOneofValue = false @@ -932,6 +1009,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .setCannedMessageModuleMessages(v) } }() + case 37: try { + var v: String? + try decoder.decodeSingularStringField(value: &v) + if let v = v { + if self.payloadVariant != nil {try decoder.handleConflictingOneOf()} + self.payloadVariant = .setRingtoneMessage(v) + } + }() case 64: try { var v: Bool? try decoder.decodeSingularBoolField(value: &v) @@ -1071,6 +1156,14 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .getDeviceMetadataResponse(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 13) }() + case .getRingtoneRequest?: try { + guard case .getRingtoneRequest(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularBoolField(value: v, fieldNumber: 14) + }() + case .getRingtoneResponse?: try { + guard case .getRingtoneResponse(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularStringField(value: v, fieldNumber: 15) + }() case .setOwner?: try { guard case .setOwner(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 32) @@ -1091,6 +1184,10 @@ extension AdminMessage: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .setCannedMessageModuleMessages(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularStringField(value: v, fieldNumber: 36) }() + case .setRingtoneMessage?: try { + guard case .setRingtoneMessage(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularStringField(value: v, fieldNumber: 37) + }() case .beginEditSettings?: try { guard case .beginEditSettings(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularBoolField(value: v, fieldNumber: 64) @@ -1165,5 +1262,6 @@ extension AdminMessage.ModuleConfigType: SwiftProtobuf._ProtoNameProviding { 5: .same(proto: "TELEMETRY_CONFIG"), 6: .same(proto: "CANNEDMSG_CONFIG"), 7: .same(proto: "AUDIO_CONFIG"), + 8: .same(proto: "REMOTEHARDWARE_CONFIG"), ] } diff --git a/Meshtastic/Protobufs/config.pb.swift b/Meshtastic/Protobufs/config.pb.swift index 3c300b12..bc6b37eb 100644 --- a/Meshtastic/Protobufs/config.pb.swift +++ b/Meshtastic/Protobufs/config.pb.swift @@ -474,7 +474,7 @@ struct Config { /// /// acquire an address via DHCP or assign static - var ethMode: Config.NetworkConfig.EthMode = .dhcp + var addressMode: Config.NetworkConfig.AddressMode = .dhcp /// /// struct to keep static address @@ -489,7 +489,7 @@ struct Config { var unknownFields = SwiftProtobuf.UnknownStorage() - enum EthMode: SwiftProtobuf.Enum { + enum AddressMode: SwiftProtobuf.Enum { typealias RawValue = Int /// @@ -592,6 +592,14 @@ struct Config { /// Override auto-detect in screen var oled: Config.DisplayConfig.OledType = .oledAuto + /// + /// Display Mode + var displaymode: Config.DisplayConfig.DisplayMode = .default + + /// + /// Print first line in pseudo-bold? FALSE is original style, TRUE is bold + var headingBold: Bool = false + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -739,6 +747,52 @@ struct Config { } + enum DisplayMode: SwiftProtobuf.Enum { + typealias RawValue = Int + + /// + /// Default. The old style for the 128x64 OLED screen + case `default` // = 0 + + /// + /// Rearrange display elements to cater for bicolor OLED displays + case twocolor // = 1 + + /// + /// Same as TwoColor, but with inverted top bar. Not so good for Epaper displays + case inverted // = 2 + + /// + /// TFT Full Color Displays (not implemented yet) + case color // = 3 + case UNRECOGNIZED(Int) + + init() { + self = .default + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .default + case 1: self = .twocolor + case 2: self = .inverted + case 3: self = .color + default: self = .UNRECOGNIZED(rawValue) + } + } + + var rawValue: Int { + switch self { + case .default: return 0 + case .twocolor: return 1 + case .inverted: return 2 + case .color: return 3 + case .UNRECOGNIZED(let i): return i + } + } + + } + init() {} } @@ -1099,9 +1153,9 @@ extension Config.PositionConfig.PositionFlags: CaseIterable { ] } -extension Config.NetworkConfig.EthMode: CaseIterable { +extension Config.NetworkConfig.AddressMode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. - static var allCases: [Config.NetworkConfig.EthMode] = [ + static var allCases: [Config.NetworkConfig.AddressMode] = [ .dhcp, .static, ] @@ -1136,6 +1190,16 @@ extension Config.DisplayConfig.OledType: CaseIterable { ] } +extension Config.DisplayConfig.DisplayMode: CaseIterable { + // The compiler won't synthesize support with the UNRECOGNIZED case. + static var allCases: [Config.DisplayConfig.DisplayMode] = [ + .default, + .twocolor, + .inverted, + .color, + ] +} + extension Config.LoRaConfig.RegionCode: CaseIterable { // The compiler won't synthesize support with the UNRECOGNIZED case. static var allCases: [Config.LoRaConfig.RegionCode] = [ @@ -1189,12 +1253,13 @@ extension Config.PositionConfig: @unchecked Sendable {} extension Config.PositionConfig.PositionFlags: @unchecked Sendable {} extension Config.PowerConfig: @unchecked Sendable {} extension Config.NetworkConfig: @unchecked Sendable {} -extension Config.NetworkConfig.EthMode: @unchecked Sendable {} +extension Config.NetworkConfig.AddressMode: @unchecked Sendable {} extension Config.NetworkConfig.IpV4Config: @unchecked Sendable {} extension Config.DisplayConfig: @unchecked Sendable {} extension Config.DisplayConfig.GpsCoordinateFormat: @unchecked Sendable {} extension Config.DisplayConfig.DisplayUnits: @unchecked Sendable {} extension Config.DisplayConfig.OledType: @unchecked Sendable {} +extension Config.DisplayConfig.DisplayMode: @unchecked Sendable {} extension Config.LoRaConfig: @unchecked Sendable {} extension Config.LoRaConfig.RegionCode: @unchecked Sendable {} extension Config.LoRaConfig.ModemPreset: @unchecked Sendable {} @@ -1607,7 +1672,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp 4: .standard(proto: "wifi_psk"), 5: .standard(proto: "ntp_server"), 6: .standard(proto: "eth_enabled"), - 7: .standard(proto: "eth_mode"), + 7: .standard(proto: "address_mode"), 8: .standard(proto: "ipv4_config"), ] @@ -1622,7 +1687,7 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp case 4: try { try decoder.decodeSingularStringField(value: &self.wifiPsk) }() case 5: try { try decoder.decodeSingularStringField(value: &self.ntpServer) }() case 6: try { try decoder.decodeSingularBoolField(value: &self.ethEnabled) }() - case 7: try { try decoder.decodeSingularEnumField(value: &self.ethMode) }() + case 7: try { try decoder.decodeSingularEnumField(value: &self.addressMode) }() case 8: try { try decoder.decodeSingularMessageField(value: &self._ipv4Config) }() default: break } @@ -1649,8 +1714,8 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if self.ethEnabled != false { try visitor.visitSingularBoolField(value: self.ethEnabled, fieldNumber: 6) } - if self.ethMode != .dhcp { - try visitor.visitSingularEnumField(value: self.ethMode, fieldNumber: 7) + if self.addressMode != .dhcp { + try visitor.visitSingularEnumField(value: self.addressMode, fieldNumber: 7) } try { if let v = self._ipv4Config { try visitor.visitSingularMessageField(value: v, fieldNumber: 8) @@ -1664,14 +1729,14 @@ extension Config.NetworkConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if lhs.wifiPsk != rhs.wifiPsk {return false} if lhs.ntpServer != rhs.ntpServer {return false} if lhs.ethEnabled != rhs.ethEnabled {return false} - if lhs.ethMode != rhs.ethMode {return false} + if lhs.addressMode != rhs.addressMode {return false} if lhs._ipv4Config != rhs._ipv4Config {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } } -extension Config.NetworkConfig.EthMode: SwiftProtobuf._ProtoNameProviding { +extension Config.NetworkConfig.AddressMode: SwiftProtobuf._ProtoNameProviding { static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 0: .same(proto: "DHCP"), 1: .same(proto: "STATIC"), @@ -1738,6 +1803,8 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp 5: .standard(proto: "flip_screen"), 6: .same(proto: "units"), 7: .same(proto: "oled"), + 8: .same(proto: "displaymode"), + 9: .standard(proto: "heading_bold"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1753,6 +1820,8 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp case 5: try { try decoder.decodeSingularBoolField(value: &self.flipScreen) }() case 6: try { try decoder.decodeSingularEnumField(value: &self.units) }() case 7: try { try decoder.decodeSingularEnumField(value: &self.oled) }() + case 8: try { try decoder.decodeSingularEnumField(value: &self.displaymode) }() + case 9: try { try decoder.decodeSingularBoolField(value: &self.headingBold) }() default: break } } @@ -1780,6 +1849,12 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if self.oled != .oledAuto { try visitor.visitSingularEnumField(value: self.oled, fieldNumber: 7) } + if self.displaymode != .default { + try visitor.visitSingularEnumField(value: self.displaymode, fieldNumber: 8) + } + if self.headingBold != false { + try visitor.visitSingularBoolField(value: self.headingBold, fieldNumber: 9) + } try unknownFields.traverse(visitor: &visitor) } @@ -1791,6 +1866,8 @@ extension Config.DisplayConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImp if lhs.flipScreen != rhs.flipScreen {return false} if lhs.units != rhs.units {return false} if lhs.oled != rhs.oled {return false} + if lhs.displaymode != rhs.displaymode {return false} + if lhs.headingBold != rhs.headingBold {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1822,6 +1899,15 @@ extension Config.DisplayConfig.OledType: SwiftProtobuf._ProtoNameProviding { ] } +extension Config.DisplayConfig.DisplayMode: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "DEFAULT"), + 1: .same(proto: "TWOCOLOR"), + 2: .same(proto: "INVERTED"), + 3: .same(proto: "COLOR"), + ] +} + extension Config.LoRaConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = Config.protoMessageName + ".LoRaConfig" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/Meshtastic/Protobufs/localonly.pb.swift b/Meshtastic/Protobufs/localonly.pb.swift index 2c89a577..741b8e2d 100644 --- a/Meshtastic/Protobufs/localonly.pb.swift +++ b/Meshtastic/Protobufs/localonly.pb.swift @@ -211,6 +211,17 @@ struct LocalModuleConfig { /// Clears the value of `audio`. Subsequent reads from it will return its default value. mutating func clearAudio() {_uniqueStorage()._audio = nil} + /// + /// The part of the config that is specific to the Remote Hardware module + var remoteHardware: ModuleConfig.RemoteHardwareConfig { + get {return _storage._remoteHardware ?? ModuleConfig.RemoteHardwareConfig()} + set {_uniqueStorage()._remoteHardware = newValue} + } + /// Returns true if `remoteHardware` has been explicitly set. + var hasRemoteHardware: Bool {return _storage._remoteHardware != nil} + /// Clears the value of `remoteHardware`. Subsequent reads from it will return its default value. + mutating func clearRemoteHardware() {_uniqueStorage()._remoteHardware = nil} + /// /// A version integer used to invalidate old save files when we make /// incompatible changes This integer is set at build time and is private to @@ -369,6 +380,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 6: .same(proto: "telemetry"), 7: .standard(proto: "canned_message"), 9: .same(proto: "audio"), + 10: .standard(proto: "remote_hardware"), 8: .same(proto: "version"), ] @@ -381,6 +393,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem var _telemetry: ModuleConfig.TelemetryConfig? = nil var _cannedMessage: ModuleConfig.CannedMessageConfig? = nil var _audio: ModuleConfig.AudioConfig? = nil + var _remoteHardware: ModuleConfig.RemoteHardwareConfig? = nil var _version: UInt32 = 0 static let defaultInstance = _StorageClass() @@ -396,6 +409,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem _telemetry = source._telemetry _cannedMessage = source._cannedMessage _audio = source._audio + _remoteHardware = source._remoteHardware _version = source._version } } @@ -424,6 +438,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem case 7: try { try decoder.decodeSingularMessageField(value: &_storage._cannedMessage) }() case 8: try { try decoder.decodeSingularUInt32Field(value: &_storage._version) }() case 9: try { try decoder.decodeSingularMessageField(value: &_storage._audio) }() + case 10: try { try decoder.decodeSingularMessageField(value: &_storage._remoteHardware) }() default: break } } @@ -463,6 +478,9 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem try { if let v = _storage._audio { try visitor.visitSingularMessageField(value: v, fieldNumber: 9) } }() + try { if let v = _storage._remoteHardware { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -480,6 +498,7 @@ extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem if _storage._telemetry != rhs_storage._telemetry {return false} if _storage._cannedMessage != rhs_storage._cannedMessage {return false} if _storage._audio != rhs_storage._audio {return false} + if _storage._remoteHardware != rhs_storage._remoteHardware {return false} if _storage._version != rhs_storage._version {return false} return true } diff --git a/Meshtastic/Protobufs/mesh.pb.swift b/Meshtastic/Protobufs/mesh.pb.swift index f0671f23..05e93acc 100644 --- a/Meshtastic/Protobufs/mesh.pb.swift +++ b/Meshtastic/Protobufs/mesh.pb.swift @@ -154,6 +154,10 @@ enum HardwareModel: SwiftProtobuf.Enum { /// New Heltec Wireless Stick Lite with ESP32-S3 CPU case heltecWslV3 // = 44 + /// + /// New BETAFPV ELRS Micro TX Module 2.4G with ESP32 CPU + case betafpv2400Tx // = 45 + /// /// 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. case privateHw // = 255 @@ -196,6 +200,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case 42: self = .m5Stack case 43: self = .heltecV3 case 44: self = .heltecWslV3 + case 45: self = .betafpv2400Tx case 255: self = .privateHw default: self = .UNRECOGNIZED(rawValue) } @@ -234,6 +239,7 @@ enum HardwareModel: SwiftProtobuf.Enum { case .m5Stack: return 42 case .heltecV3: return 43 case .heltecWslV3: return 44 + case .betafpv2400Tx: return 45 case .privateHw: return 255 case .UNRECOGNIZED(let i): return i } @@ -277,6 +283,7 @@ extension HardwareModel: CaseIterable { .m5Stack, .heltecV3, .heltecWslV3, + .betafpv2400Tx, .privateHw, ] } @@ -1772,6 +1779,28 @@ extension LogRecord.Level: CaseIterable { #endif // swift(>=4.2) +struct QueueStatus { + // 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. + + /// Last attempt to queue status, ErrorCode + var res: Int32 = 0 + + /// Free entries in the outgoing queue + var free: UInt32 = 0 + + /// Maximum entries in the outgoing queue + var maxlen: UInt32 = 0 + + /// What was mesh packet id that generated this response? + var meshPacketID: UInt32 = 0 + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + /// /// Packets from the radio to the phone will appear on the fromRadio characteristic. /// It will support READ and NOTIFY. When a new packet arrives the device will BLE notify? @@ -1895,6 +1924,15 @@ struct FromRadio { set {_uniqueStorage()._payloadVariant = .channel(newValue)} } + /// Queue status info + var queueStatus: QueueStatus { + get { + if case .queueStatus(let v)? = _storage._payloadVariant {return v} + return QueueStatus() + } + set {_uniqueStorage()._payloadVariant = .queueStatus(newValue)} + } + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -1935,6 +1973,8 @@ struct FromRadio { /// /// One packet is sent for each channel case channel(Channel) + /// Queue status info + case queueStatus(QueueStatus) #if !swift(>=4.1) static func ==(lhs: FromRadio.OneOf_PayloadVariant, rhs: FromRadio.OneOf_PayloadVariant) -> Bool { @@ -1978,6 +2018,10 @@ struct FromRadio { guard case .channel(let l) = lhs, case .channel(let r) = rhs else { preconditionFailure() } return l == r }() + case (.queueStatus, .queueStatus): return { + guard case .queueStatus(let l) = lhs, case .queueStatus(let r) = rhs else { preconditionFailure() } + return l == r + }() default: return false } } @@ -2133,6 +2177,7 @@ extension NodeInfo: @unchecked Sendable {} extension MyNodeInfo: @unchecked Sendable {} extension LogRecord: @unchecked Sendable {} extension LogRecord.Level: @unchecked Sendable {} +extension QueueStatus: @unchecked Sendable {} extension FromRadio: @unchecked Sendable {} extension FromRadio.OneOf_PayloadVariant: @unchecked Sendable {} extension ToRadio: @unchecked Sendable {} @@ -2175,6 +2220,7 @@ extension HardwareModel: SwiftProtobuf._ProtoNameProviding { 42: .same(proto: "M5STACK"), 43: .same(proto: "HELTEC_V3"), 44: .same(proto: "HELTEC_WSL_V3"), + 45: .same(proto: "BETAFPV_2400_TX"), 255: .same(proto: "PRIVATE_HW"), ] } @@ -3245,6 +3291,56 @@ extension LogRecord.Level: SwiftProtobuf._ProtoNameProviding { ] } +extension QueueStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = "QueueStatus" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "res"), + 2: .same(proto: "free"), + 3: .same(proto: "maxlen"), + 4: .standard(proto: "mesh_packet_id"), + ] + + 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.decodeSingularInt32Field(value: &self.res) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self.free) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self.maxlen) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.meshPacketID) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.res != 0 { + try visitor.visitSingularInt32Field(value: self.res, fieldNumber: 1) + } + if self.free != 0 { + try visitor.visitSingularUInt32Field(value: self.free, fieldNumber: 2) + } + if self.maxlen != 0 { + try visitor.visitSingularUInt32Field(value: self.maxlen, fieldNumber: 3) + } + if self.meshPacketID != 0 { + try visitor.visitSingularUInt32Field(value: self.meshPacketID, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: QueueStatus, rhs: QueueStatus) -> Bool { + if lhs.res != rhs.res {return false} + if lhs.free != rhs.free {return false} + if lhs.maxlen != rhs.maxlen {return false} + if lhs.meshPacketID != rhs.meshPacketID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = "FromRadio" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -3258,6 +3354,7 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation 8: .same(proto: "rebooted"), 9: .same(proto: "moduleConfig"), 10: .same(proto: "channel"), + 11: .same(proto: "queueStatus"), ] fileprivate class _StorageClass { @@ -3397,6 +3494,19 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation _storage._payloadVariant = .channel(v) } }() + case 11: try { + var v: QueueStatus? + var hadOneofValue = false + if let current = _storage._payloadVariant { + hadOneofValue = true + if case .queueStatus(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._payloadVariant = .queueStatus(v) + } + }() default: break } } @@ -3449,6 +3559,10 @@ extension FromRadio: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementation guard case .channel(let v)? = _storage._payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 10) }() + case .queueStatus?: try { + guard case .queueStatus(let v)? = _storage._payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + }() case nil: break } } diff --git a/Meshtastic/Protobufs/module_config.pb.swift b/Meshtastic/Protobufs/module_config.pb.swift index 19fa66f7..74a8e7a1 100644 --- a/Meshtastic/Protobufs/module_config.pb.swift +++ b/Meshtastic/Protobufs/module_config.pb.swift @@ -111,6 +111,16 @@ struct ModuleConfig { set {payloadVariant = .audio(newValue)} } + /// + /// TODO: REPLACE + var remoteHardware: ModuleConfig.RemoteHardwareConfig { + get { + if case .remoteHardware(let v)? = payloadVariant {return v} + return ModuleConfig.RemoteHardwareConfig() + } + set {payloadVariant = .remoteHardware(newValue)} + } + var unknownFields = SwiftProtobuf.UnknownStorage() /// @@ -140,6 +150,9 @@ struct ModuleConfig { /// /// TODO: REPLACE case audio(ModuleConfig.AudioConfig) + /// + /// TODO: REPLACE + case remoteHardware(ModuleConfig.RemoteHardwareConfig) #if !swift(>=4.1) static func ==(lhs: ModuleConfig.OneOf_PayloadVariant, rhs: ModuleConfig.OneOf_PayloadVariant) -> Bool { @@ -179,6 +192,10 @@ struct ModuleConfig { guard case .audio(let l) = lhs, case .audio(let r) = rhs else { preconditionFailure() } return l == r }() + case (.remoteHardware, .remoteHardware): return { + guard case .remoteHardware(let l) = lhs, case .remoteHardware(let r) = rhs else { preconditionFailure() } + return l == r + }() default: return false } } @@ -230,6 +247,22 @@ struct ModuleConfig { init() {} } + /// + /// RemoteHardwareModule Config + struct RemoteHardwareConfig { + // 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. + + /// + /// Whether the Module is enabled + var enabled: Bool = false + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + } + /// /// Audio Config for codec2 voice struct AudioConfig { @@ -844,6 +877,7 @@ extension ModuleConfig.CannedMessageConfig.InputEventChar: CaseIterable { extension ModuleConfig: @unchecked Sendable {} extension ModuleConfig.OneOf_PayloadVariant: @unchecked Sendable {} extension ModuleConfig.MQTTConfig: @unchecked Sendable {} +extension ModuleConfig.RemoteHardwareConfig: @unchecked Sendable {} extension ModuleConfig.AudioConfig: @unchecked Sendable {} extension ModuleConfig.AudioConfig.Audio_Baud: @unchecked Sendable {} extension ModuleConfig.SerialConfig: @unchecked Sendable {} @@ -870,6 +904,7 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat 6: .same(proto: "telemetry"), 7: .standard(proto: "canned_message"), 8: .same(proto: "audio"), + 9: .standard(proto: "remote_hardware"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -982,6 +1017,19 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat self.payloadVariant = .audio(v) } }() + case 9: try { + var v: ModuleConfig.RemoteHardwareConfig? + var hadOneofValue = false + if let current = self.payloadVariant { + hadOneofValue = true + if case .remoteHardware(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.payloadVariant = .remoteHardware(v) + } + }() default: break } } @@ -1025,6 +1073,10 @@ extension ModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementat guard case .audio(let v)? = self.payloadVariant else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 8) }() + case .remoteHardware?: try { + guard case .remoteHardware(let v)? = self.payloadVariant else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -1099,6 +1151,38 @@ extension ModuleConfig.MQTTConfig: SwiftProtobuf.Message, SwiftProtobuf._Message } } +extension ModuleConfig.RemoteHardwareConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = ModuleConfig.protoMessageName + ".RemoteHardwareConfig" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "enabled"), + ] + + 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.decodeSingularBoolField(value: &self.enabled) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if self.enabled != false { + try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: ModuleConfig.RemoteHardwareConfig, rhs: ModuleConfig.RemoteHardwareConfig) -> Bool { + if lhs.enabled != rhs.enabled {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension ModuleConfig.AudioConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = ModuleConfig.protoMessageName + ".AudioConfig" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index 16afc341..ead9c3a6 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -54,6 +54,10 @@ struct BluetoothConfig: View { TextField("bluetooth.mode.fixedpin", text: $fixedPin) .foregroundColor(.gray) .onChange(of: fixedPin, perform: { value in + // Don't let the first character be 0 because it will get stripped when saving a UInt32 + if fixedPin.first == "0" { + fixedPin = fixedPin.replacing("0", with: "") + } //Require that pin is no more than 6 numbers and no less than 6 numbers if fixedPin.utf8.count == pinLength { shortPin = false diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 58c022f0..8f57c6b3 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -114,7 +114,7 @@ struct NetworkConfig: View { network.wifiSsid = self.wifiSsid network.wifiPsk = self.wifiPsk network.ethEnabled = self.ethEnabled - network.ethMode = Config.NetworkConfig.EthMode.dhcp + //network.addressMode = Config.NetworkConfig.AddressMode.dhcp let adminMessageId = bleManager.saveWiFiConfig(config: network, fromUser: node!.user!, toUser: node!.user!) if adminMessageId > 0 { From cd0fdec368bed172cbbb8046169fd399b45ff460 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 9 Jan 2023 18:34:43 -0800 Subject: [PATCH 39/40] localize save config string --- Meshtastic/Views/Settings/Config/BluetoothConfig.swift | 4 +++- Meshtastic/Views/Settings/Config/DeviceConfig.swift | 4 +++- Meshtastic/Views/Settings/Config/DisplayConfig.swift | 5 +++-- Meshtastic/Views/Settings/Config/LoRaConfig.swift | 4 +++- .../Views/Settings/Config/Module/CannedMessagesConfig.swift | 5 +++-- .../Settings/Config/Module/ExternalNotificationConfig.swift | 4 +++- Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift | 4 +++- .../Views/Settings/Config/Module/RangeTestConfig.swift | 4 +++- Meshtastic/Views/Settings/Config/Module/SerialConfig.swift | 5 +++-- .../Views/Settings/Config/Module/TelemetryConfig.swift | 4 +++- Meshtastic/Views/Settings/Config/NetworkConfig.swift | 4 +++- Meshtastic/Views/Settings/Config/PositionConfig.swift | 4 +++- de.lproj/Localizable.strings | 1 + en.lproj/Localizable.strings | 1 + 14 files changed, 38 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift index ead9c3a6..dcc10981 100644 --- a/Meshtastic/Views/Settings/Config/BluetoothConfig.swift +++ b/Meshtastic/Views/Settings/Config/BluetoothConfig.swift @@ -96,7 +96,9 @@ struct BluetoothConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown"))") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var bc = Config.BluetoothConfig() bc.enabled = enabled bc.mode = BluetoothModes(rawValue: mode)?.protoEnumValue() ?? Config.BluetoothConfig.PairingMode.randomPin diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 0fb1e14c..fcb55ad1 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -156,7 +156,9 @@ struct DeviceConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Device Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var dc = Config.DeviceConfig() dc.role = DeviceRoles(rawValue: deviceRole)!.protoEnumValue() diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index dfda6988..9103c5ce 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -106,8 +106,9 @@ struct DisplayConfig: View { "are.you.sure", isPresented: $isPresentingSaveConfirm ) { - Button("Save Display Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var dc = Config.DisplayConfig() dc.gpsFormat = GpsFormats(rawValue: gpsFormat)!.protoEnumValue() dc.screenOnSecs = UInt32(screenOnSeconds) diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index ed618aff..dc4ea18e 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -75,7 +75,9 @@ struct LoRaConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var lc = Config.LoRaConfig() lc.hopLimit = UInt32(hopLimit) lc.region = RegionCodes(rawValue: region)!.protoEnumValue() diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index c7966528..46071f37 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -218,8 +218,9 @@ struct CannedMessagesConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Canned Messages Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { if hasChanges { var cmc = ModuleConfig.CannedMessageConfig() cmc.enabled = enabled diff --git a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift index a8df56dc..90b8e759 100644 --- a/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/ExternalNotificationConfig.swift @@ -154,7 +154,9 @@ struct ExternalNotificationConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save External Notification Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var enc = ModuleConfig.ExternalNotificationConfig() enc.enabled = enabled enc.alertBell = alertBell diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index a56d092b..54f63e6d 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -143,7 +143,9 @@ struct MQTTConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save MQTT Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var mqtt = ModuleConfig.MQTTConfig() mqtt.enabled = self.enabled mqtt.address = self.address diff --git a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift index e6677593..9fbd5ffd 100644 --- a/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/RangeTestConfig.swift @@ -62,7 +62,9 @@ struct RangeTestConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Range Test Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var rtc = ModuleConfig.RangeTestConfig() rtc.enabled = enabled rtc.save = save diff --git a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift index a3d50d07..e515244c 100644 --- a/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/SerialConfig.swift @@ -118,8 +118,9 @@ struct SerialConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Serial Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { - + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var sc = ModuleConfig.SerialConfig() sc.enabled = enabled sc.echo = echo diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index af1cfb79..c803add3 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -77,7 +77,9 @@ struct TelemetryConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Telemetry Module Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var tc = ModuleConfig.TelemetryConfig() tc.deviceUpdateInterval = UInt32(deviceUpdateInterval) tc.environmentUpdateInterval = UInt32(environmentUpdateInterval) diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index 8f57c6b3..65e73b2c 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -108,7 +108,9 @@ struct NetworkConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Config for \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { var network = Config.NetworkConfig() network.wifiEnabled = self.wifiEnabled network.wifiSsid = self.wifiSsid diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index fc203b54..8600dd53 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -205,7 +205,9 @@ struct PositionConfig: View { isPresented: $isPresentingSaveConfirm, titleVisibility: .visible ) { - Button("Save Position Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + let nodeName = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : NSLocalizedString("unknown", comment: "Unknown") + let buttonText = String.localizedStringWithFormat(NSLocalizedString("save.config %@", comment: "Save Config for %@"), nodeName) + Button(buttonText) { if fixedPosition { _ = bleManager.sendPosition(destNum: bleManager.connectedPeripheral.num, wantResponse: false) diff --git a/de.lproj/Localizable.strings b/de.lproj/Localizable.strings index 38ccc1ba..54aeb93d 100644 --- a/de.lproj/Localizable.strings +++ b/de.lproj/Localizable.strings @@ -215,6 +215,7 @@ "routing.notauthorized"="Nicht authorisiert"; "satellite"="Satellit"; "save"="Speichern"; +"save.config %@"="Save Config for %@"; "serial"="Serial"; "serial.config"="Serial Konfiguration"; "serial.mode.default"="Standard"; diff --git a/en.lproj/Localizable.strings b/en.lproj/Localizable.strings index 26240362..6b772ea7 100644 --- a/en.lproj/Localizable.strings +++ b/en.lproj/Localizable.strings @@ -215,6 +215,7 @@ "routing.notauthorized"="Not Authorized"; "satellite"="Satellite"; "save"="Save"; +"save.config %@"="Save Config for %@"; "serial"="Serial"; "serial.config"="Serial Config"; "serial.mode.default"="Default"; From 8bc645412b3f601c404e191d15d384ced7e74cdf Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 10 Jan 2023 06:49:19 -0800 Subject: [PATCH 40/40] Cluster for map pins on the node details view --- Meshtastic.xcodeproj/project.pbxproj | 12 +- .../Persistence/PositionEntityExtension.swift | 13 +- Meshtastic/Views/Map/MapView.swift | 173 ------------------ Meshtastic/Views/Map/MapViewModule.swift | 2 +- Meshtastic/Views/Map/MapViewSwiftUI.swift | 57 ++++++ Meshtastic/Views/Nodes/NodeDetail.swift | 149 +++++++-------- Meshtastic/Views/Nodes/NodeList.swift | 4 +- 7 files changed, 144 insertions(+), 266 deletions(-) delete mode 100644 Meshtastic/Views/Map/MapView.swift create mode 100644 Meshtastic/Views/Map/MapViewSwiftUI.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index d28ae0a6..27ac9bee 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - C9483F6D2773017500998F6B /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9483F6C2773017500998F6B /* MapView.swift */; }; C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */; }; @@ -22,6 +21,7 @@ DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; }; DD2553572855B02500E55709 /* LoRaConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553562855B02500E55709 /* LoRaConfig.swift */; }; DD2553592855B52700E55709 /* PositionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2553582855B52700E55709 /* PositionConfig.swift */; }; + DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */; }; DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeDetail.swift */; }; DD3501892852FC3B000FC853 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3501882852FC3B000FC853 /* Settings.swift */; }; DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35018A2852FC79000FC853 /* UserSettings.swift */; }; @@ -127,7 +127,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - C9483F6C2773017500998F6B /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAnnotationView.swift; sourceTree = ""; }; C9A88B54278B503C00BD810A /* MapViewModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapViewModule.swift; sourceTree = ""; }; @@ -141,6 +140,7 @@ DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = ""; }; DD2553562855B02500E55709 /* LoRaConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoRaConfig.swift; sourceTree = ""; }; DD2553582855B52700E55709 /* PositionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionConfig.swift; sourceTree = ""; }; + DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewSwiftUI.swift; sourceTree = ""; }; DD2E65252767A01F00E45FC5 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; DD3501882852FC3B000FC853 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; DD35018A2852FC79000FC853 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; @@ -270,9 +270,9 @@ isa = PBXGroup; children = ( C9A7BC0E27759A6800760B50 /* Custom */, - C9483F6C2773017500998F6B /* MapView.swift */, C9A88B54278B503C00BD810A /* MapViewModule.swift */, C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */, + DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */, ); path = Map; sourceTree = ""; @@ -744,6 +744,7 @@ DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, + DD2AD8A8296D2DF9001FF0E7 /* MapViewSwiftUI.swift in Sources */, DD6193792863875F00E59241 /* SerialConfig.swift in Sources */, DDA0B6B2294CDC55001356EC /* Channels.swift in Sources */, DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */, @@ -783,7 +784,6 @@ DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */, DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */, DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */, - C9483F6D2773017500998F6B /* MapView.swift in Sources */, DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */, DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */, DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */, @@ -1006,7 +1006,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.10; + MARKETING_VERSION = 2.0.11; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; @@ -1039,7 +1039,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.0.10; + MARKETING_VERSION = 2.0.11; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; diff --git a/Meshtastic/Persistence/PositionEntityExtension.swift b/Meshtastic/Persistence/PositionEntityExtension.swift index a17f7e06..05fb5d59 100644 --- a/Meshtastic/Persistence/PositionEntityExtension.swift +++ b/Meshtastic/Persistence/PositionEntityExtension.swift @@ -23,7 +23,7 @@ extension PositionEntity { return d / 1e7 } - var coordinate: CLLocationCoordinate2D? { + var nodeCoordinate: CLLocationCoordinate2D? { if latitudeI != 0 && longitudeI != 0 { let coord = CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!) return coord @@ -31,12 +31,17 @@ extension PositionEntity { return nil } } - + var annotaton: MKPointAnnotation { let pointAnn = MKPointAnnotation() - if coordinate != nil { - pointAnn.coordinate = coordinate! + if nodeCoordinate != nil { + pointAnn.coordinate = nodeCoordinate! } return pointAnn } } + +extension PositionEntity: MKAnnotation { + public var coordinate: CLLocationCoordinate2D { nodeCoordinate! } + public var subtitle: String? { time?.formatted() } +} diff --git a/Meshtastic/Views/Map/MapView.swift b/Meshtastic/Views/Map/MapView.swift deleted file mode 100644 index 02c752be..00000000 --- a/Meshtastic/Views/Map/MapView.swift +++ /dev/null @@ -1,173 +0,0 @@ -// -// MapView.swift -// MeshtasticApple -// -// Created by Joshua Pirihi on 22/12/21. -// - -import Foundation -import UIKit -import MapKit -import SwiftUI -import CoreData -#if false -// wrap a MKMapView into something we can use in SwiftUI -struct MapView: UIViewRepresentable { - - var nodes: FetchedResults - - var mapViewDelegate = MapViewDelegate() - - // observe changes to the key in UserDefaults - @AppStorage("meshMapType") var type: String = "hybrid" - - func makeUIView(context: Context) -> MKMapView { - - let map = MKMapView(frame: .zero) - - map.userTrackingMode = .follow - - let region = MKCoordinateRegion( center: map.centerCoordinate, latitudinalMeters: CLLocationDistance(exactly: 500)!, longitudinalMeters: CLLocationDistance(exactly: 500)!) - map.setRegion(map.regionThatFits(region), animated: false) - - //self.updateMapType(map) - self.showNodePositions(to: map) - self.moveToMeshRegion(in: map) - - map.register(PositionAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(PositionAnnotationView.self)) - - let overlay = MKTileOverlay(urlTemplate: //"http://tiles-a.data-cdn.linz.govt.nz/services;key=7fa19132d53240708c4ff436df5b9800/tiles/v4/layer=50767/EPSG:3857/{z}/{x}/{y}.png") - "http://10.147.253.250:5050/local/map/{z}/{x}/{y}.png") - overlay.canReplaceMapContent = true - self.mapViewDelegate.renderer = MKTileOverlayRenderer(tileOverlay: overlay) - map.addOverlay(overlay) - - return map - } - - func updateUIView(_ view: MKMapView, context: Context) { - view.delegate = mapViewDelegate // (1) This should be set in makeUIView, but it is getting reset to `nil` - view.translatesAutoresizingMaskIntoConstraints = false // (2) In the absence of this, we get constraints error on rotation; and again, it seems one should do this in makeUIView, but has to be here - - self.updateMapType(view) - - self.showNodePositions(to: view) - - //if (self.needToMoveToMeshRegion) { - // self.moveToMeshRegion(in: view) - // self.needToMoveToMeshRegion = false - //} - } - - func moveToMeshRegion(in mapView: MKMapView) { - //go through the annotations and create a bounding box that encloses them - - var minLat: CLLocationDegrees = 90.0 - var maxLat: CLLocationDegrees = -90.0 - var minLon: CLLocationDegrees = 180.0 - var maxLon: CLLocationDegrees = -180.0 - - for annotation in mapView.annotations { - if annotation.isKind(of: PositionAnnotation.self) { - minLat = min(minLat, annotation.coordinate.latitude) - maxLat = max(maxLat, annotation.coordinate.latitude) - minLon = min(minLon, annotation.coordinate.longitude) - maxLon = max(maxLon, annotation.coordinate.longitude) - } - } - - //check if the mesh region looks sensible before we move to it. Otherwise we won't move the map (leave it at the current location) - if maxLat < minLat || (maxLat-minLat) > 5 || maxLon < minLon || (maxLon-minLon) > 5 { - return - } - - let centerCoord = CLLocationCoordinate2D(latitude: (minLat+maxLat)/2, longitude: (minLon+maxLon)/2) - - let span = MKCoordinateSpan(latitudeDelta: (maxLat-minLat)*1.5, longitudeDelta: (maxLon-minLon)*1.5) - - let region = mapView.regionThatFits(MKCoordinateRegion(center: centerCoord, span: span)) - - mapView.setRegion(region, animated: true) - - - } - - func updateMapType(_ map: MKMapView) { - - switch self.type { - case "satellite": - map.mapType = .satellite - break - case "standard": - map.mapType = .standard - break - case "hybrid": - map.mapType = .hybrid - break - default: - map.mapType = .hybrid - } - } -} - -private extension MapView { - - func showNodePositions(to view: MKMapView) { - - // clear any existing annotations - if !view.annotations.isEmpty { - view.removeAnnotations(view.annotations) - } - - for node in self.nodes { - // try and get the last position - if (node.positions?.count ?? 0) > 0 && (node.positions!.lastObject as! PositionEntity).coordinate != nil { - let annotation = PositionAnnotation() - annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate! - annotation.title = node.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") - annotation.shortName = node.user?.shortName?.uppercased() ?? "???" - - view.addAnnotation(annotation) - } - } - } -} - -class MapViewDelegate: NSObject, MKMapViewDelegate { - - var renderer: MKTileOverlayRenderer? - - func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { - - guard !annotation.isKind(of: MKUserLocation.self) else { - // Make a fast exit if the annotation is the `MKUserLocation`, as it's not an annotation view we wish to customize. - return nil - } - - var annotationView: MKAnnotationView? - - if let annotation = annotation as? PositionAnnotation { - annotationView = self.setupPositionAnnotationView(for: annotation, on: mapView) - } - - return annotationView - } - - func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { - return self.renderer! - - } - - private func setupPositionAnnotationView(for annotation: PositionAnnotation, on mapView: MKMapView) -> PositionAnnotationView { - let identifier = NSStringFromClass(PositionAnnotationView.self) - - let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? PositionAnnotationView ?? PositionAnnotationView() - - annotationView.name = annotation.shortName ?? "???" - - annotationView.canShowCallout = true - - return annotationView - } -} -#endif diff --git a/Meshtastic/Views/Map/MapViewModule.swift b/Meshtastic/Views/Map/MapViewModule.swift index 13d5bb77..bb3bfa82 100644 --- a/Meshtastic/Views/Map/MapViewModule.swift +++ b/Meshtastic/Views/Map/MapViewModule.swift @@ -223,7 +223,7 @@ public struct MapView: UIViewRepresentable { } let annotation = PositionAnnotation() - annotation.coordinate = position.coordinate! + annotation.coordinate = position.nodeCoordinate! annotation.title = position.nodePosition!.user?.longName ?? NSLocalizedString("unknown", comment: "Unknown") annotation.shortName = position.nodePosition!.user?.shortName?.uppercased() ?? "???" diff --git a/Meshtastic/Views/Map/MapViewSwiftUI.swift b/Meshtastic/Views/Map/MapViewSwiftUI.swift new file mode 100644 index 00000000..e02ca6da --- /dev/null +++ b/Meshtastic/Views/Map/MapViewSwiftUI.swift @@ -0,0 +1,57 @@ +// +// MapViewSwitUI.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 1/9/23. +// + +import SwiftUI +import MapKit + +struct MapViewSwiftUI: UIViewRepresentable { + + let positions: [PositionEntity] + let region: MKCoordinateRegion + let mapViewType: MKMapType + + func makeUIView(context: Context) -> MKMapView { + let mapView = MKMapView() + mapView.mapType = mapViewType + mapView.setRegion(region, animated: true) + mapView.isRotateEnabled = true + mapView.addAnnotations(positions) + mapView.delegate = context.coordinator + return mapView + } + + func updateUIView(_ mapView: MKMapView, context: Context) { + mapView.mapType = mapViewType + } + + func makeCoordinator() -> MapCoordinator { + .init() + } + + final class MapCoordinator: NSObject, MKMapViewDelegate { + + func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { + + switch annotation { + + case _ as MKClusterAnnotation: + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "nodeGroup") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "nodeGroup") + annotationView.markerTintColor = .darkGray + return annotationView + case _ as PositionEntity: + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "node") as? MKMarkerAnnotationView ?? MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: "Node") + annotationView.canShowCallout = true + annotationView.glyphText = "📟" + annotationView.clusteringIdentifier = "nodeGroup" + annotationView.markerTintColor = UIColor(.accentColor) + annotationView.titleVisibility = .visible + return annotationView + default: return nil + } + } + } +} diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 932ae13a..bae52138 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -1,73 +1,62 @@ /* -Abstract: -A view showing the details for a node. -*/ + Abstract: + A view showing the details for a node. + */ import SwiftUI import MapKit import CoreLocation struct NodeDetail: View { - + @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @State private var mapType: MKMapType = .standard + @State private var showingDetailsPopover = false @State var satsInView = 0 @State private var showingShutdownConfirm: Bool = false @State private var showingRebootConfirm: Bool = false - + var node: NodeInfoEntity - + var body: some View { let hwModelString = node.user?.hwModel ?? "UNSET" - + NavigationStack { GeometryReader { bounds in VStack { if node.positions?.count ?? 0 > 0 { let mostRecent = node.positions?.lastObject as! PositionEntity - if mostRecent.coordinate != nil { - let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!) - - let regionBinding = Binding( - get: { - MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)) - }, - set: { _ in } - ) + let nodeCoordinatePosition = CLLocationCoordinate2D(latitude: mostRecent.latitude!, longitude: mostRecent.longitude!) + ZStack { + let annotations = node.positions?.array as! [PositionEntity] ZStack { - let annotations = node.positions?.array as! [PositionEntity] - - Map(coordinateRegion: regionBinding, - interactionModes: [.all], - showsUserLocation: true, - userTrackingMode: .constant(.follow), - annotationItems: annotations) { location in + MapViewSwiftUI(positions: annotations, region: MKCoordinateRegion(center: nodeCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005)), mapViewType: mapType) + VStack { + Spacer() + Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ") + .font(.caption2) - return MapAnnotation( - coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0), - content: { - - NodeAnnotation(time: location.time!) - } - ) + Picker("", selection: $mapType) { + Text("Standard").tag(MKMapType.standard) + Text("Hybrid").tag(MKMapType.hybrid) + Text("Satellite").tag(MKMapType.satellite) + } + .pickerStyle(SegmentedPickerStyle()) } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2) - } - Text(mostRecent.satsInView > 0 ? "Sats: \(mostRecent.satsInView)" : " ") - .offset( y:-40) + .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.65) } - } else { HStack { - + } - .padding([.top], 60) + .padding([.top], 20) } ScrollView { @@ -80,13 +69,13 @@ struct NodeDetail: View { Divider() VStack { if node.user != nil { - + Image(hwModelString) .resizable() .aspectRatio(contentMode: .fill) .frame(width: 100, height: 100) .cornerRadius(5) - + Text(String(hwModelString)) .foregroundColor(.gray) .font(.largeTitle).fixedSize() @@ -96,7 +85,7 @@ struct NodeDetail: View { if node.snr > 0 { Divider() VStack(alignment: .center) { - + Image(systemName: "waveform.path") .font(.title) .foregroundColor(.accentColor) @@ -109,15 +98,15 @@ struct NodeDetail: View { .fixedSize() } } - + if node.telemetries?.count ?? 0 >= 1 { - + let mostRecent = node.telemetries?.lastObject as! TelemetryEntity Divider() VStack(alignment: .center) { BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel)) if mostRecent.voltage > 0 { - + Text(String(format: "%.2f", mostRecent.voltage) + " V") .font(.title) .foregroundColor(.gray) @@ -147,7 +136,7 @@ struct NodeDetail: View { Divider() VStack { HStack { - Image(systemName: "number") + Image(systemName: "number") .font(.title2) .foregroundColor(.accentColor) .symbolRenderingMode(.hierarchical) @@ -160,8 +149,8 @@ struct NodeDetail: View { HStack { Image(systemName: "globe") .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) Text("MAC Address: ").font(.title) } @@ -174,8 +163,8 @@ struct NodeDetail: View { HStack { Image(systemName: "clock.badge.checkmark.fill") .font(.title) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) Text("heard.last").font(.title)+Text(":").font(.title) } @@ -190,7 +179,7 @@ struct NodeDetail: View { } else { HStack { - + VStack(alignment: .center) { CircleText(text: node.user?.shortName ?? "???", color: .accentColor) } @@ -210,7 +199,7 @@ struct NodeDetail: View { if node.snr > 0 { Divider() VStack(alignment: .center) { - + Image(systemName: "waveform.path") .font(.title) .foregroundColor(.accentColor) @@ -223,15 +212,15 @@ struct NodeDetail: View { } .padding(5) } - + if node.telemetries?.count ?? 0 >= 1 { - + let mostRecent = node.telemetries?.lastObject as! TelemetryEntity - + Divider() - + VStack(alignment: .center) { - + BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel)) if mostRecent.voltage > 0 { @@ -262,7 +251,7 @@ struct NodeDetail: View { Divider() VStack { HStack { - Image(systemName: "number") + Image(systemName: "number") .font(.title2) .foregroundColor(.accentColor) .symbolRenderingMode(.hierarchical) @@ -275,9 +264,9 @@ struct NodeDetail: View { Divider() HStack { Image(systemName: "globe") - .font(.headline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) + .font(.headline) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) Text("MAC Address: ") Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) } @@ -334,16 +323,16 @@ struct NodeDetail: View { } if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { - + HStack { if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { - + Button(action: { showingShutdownConfirm = true }) { - + Label("Power Off", systemImage: "power") } .buttonStyle(.bordered) @@ -361,13 +350,13 @@ struct NodeDetail: View { } } } - + Button(action: { showingRebootConfirm = true }) { - + Label("reboot", systemImage: "arrow.triangle.2.circlepath") } .buttonStyle(.bordered) @@ -376,31 +365,31 @@ struct NodeDetail: View { .padding() .confirmationDialog("are.you.sure", - isPresented: $showingRebootConfirm - ) { - Button("reboot.node", role: .destructive) { - - if !bleManager.sendReboot(fromUser: node.user!, toUser: node.user!) { - print("Reboot Failed") - } - } + isPresented: $showingRebootConfirm + ) { + Button("reboot.node", role: .destructive) { + + if !bleManager.sendReboot(fromUser: node.user!, toUser: node.user!) { + print("Reboot Failed") } + } + } } .padding(5) } } - .offset( y:-40) + //.offset( y:-40) } .edgesIgnoringSafeArea([.leading, .trailing]) .navigationBarTitle(String(node.user?.longName ?? NSLocalizedString("unknown", comment: "")), displayMode: .inline) .padding(.bottom, 10) .navigationBarItems(trailing: - ZStack { - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - } + ZStack { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + } ) .onAppear { self.bleManager.context = context diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 1925c767..fb6980d7 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -53,8 +53,8 @@ struct NodeList: View { HStack(alignment: .bottom) { let lastPostion = node.positions!.reversed()[0] as! PositionEntity let myCoord = CLLocation(latitude: LocationHelper.currentLocation.latitude, longitude: LocationHelper.currentLocation.longitude) - if lastPostion.coordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { - let nodeCoord = CLLocation(latitude: lastPostion.coordinate!.latitude, longitude: lastPostion.coordinate!.longitude) + if lastPostion.nodeCoordinate != nil && myCoord.coordinate.longitude != LocationHelper.DefaultLocation.longitude && myCoord.coordinate.latitude != LocationHelper.DefaultLocation.latitude { + let nodeCoord = CLLocation(latitude: lastPostion.nodeCoordinate!.latitude, longitude: lastPostion.nodeCoordinate!.longitude) let metersAway = nodeCoord.distance(from: myCoord) Image(systemName: "lines.measurement.horizontal") .font(.title3)