From b6e7f614a52aaf166943a1207d3f916000e97bb4 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 28 May 2022 01:19:44 -0700 Subject: [PATCH 01/12] Map annotations for node details --- Meshtastic Client.xcodeproj/project.pbxproj | 8 ++++-- .../Helpers/LocalNotificationManager.swift | 2 -- MeshtasticClient/Helpers/MeshPackets.swift | 4 +-- .../Views/Helpers/NodeAnnotation.swift | 26 +++++++++++++++++++ MeshtasticClient/Views/Nodes/NodeDetail.swift | 2 +- 5 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 MeshtasticClient/Views/Helpers/NodeAnnotation.swift diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index d93f6c14..3782365f 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */; }; DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; }; + DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */; }; DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; }; DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */; }; DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5C26ED09490058C060 /* portnums.pb.swift */; }; @@ -110,6 +111,7 @@ DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreDataSample.xcdatamodel; sourceTree = ""; }; DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = ""; }; + DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAnnotation.swift; sourceTree = ""; }; DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = ""; }; DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mesh.pb.swift; sourceTree = ""; }; DDAF8C5C26ED09490058C060 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = ""; }; @@ -358,6 +360,7 @@ DD90860B26F684AF00DC5189 /* BatteryIcon.swift */, DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, DDC3B273283F411B00AC321C /* LastHeardText.swift */, + DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */, ); path = Helpers; sourceTree = ""; @@ -546,6 +549,7 @@ DD4DED9027AD2975004BA27E /* cannedmessages.pb.swift in Sources */, DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */, DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */, + DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */, DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */, DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, @@ -751,7 +755,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 8; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -783,7 +787,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 8; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; diff --git a/MeshtasticClient/Helpers/LocalNotificationManager.swift b/MeshtasticClient/Helpers/LocalNotificationManager.swift index ce4cfff1..d6bea32b 100644 --- a/MeshtasticClient/Helpers/LocalNotificationManager.swift +++ b/MeshtasticClient/Helpers/LocalNotificationManager.swift @@ -44,8 +44,6 @@ class LocalNotificationManager { UNUserNotificationCenter.current().add(request) { error in guard error == nil else { return } - - print("Notification scheduled! --- ID = \(notification.id)") } } } diff --git a/MeshtasticClient/Helpers/MeshPackets.swift b/MeshtasticClient/Helpers/MeshPackets.swift index 39c1e897..f2cf5c50 100644 --- a/MeshtasticClient/Helpers/MeshPackets.swift +++ b/MeshtasticClient/Helpers/MeshPackets.swift @@ -8,8 +8,6 @@ import Foundation import CoreData - - func nodeInfoPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { let fetchNodeInfoAppRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") @@ -186,6 +184,8 @@ func telemetryPacket(packet: MeshPacket, meshLogging: Bool, context: NSManagedOb let telemetry = TelemetryEntity(context: context) + + if meshLogging { MeshLogger.log("ℹ️ MESH PACKET received for Telemetry App UNHANDLED \(telemetryMessage)") } } else { diff --git a/MeshtasticClient/Views/Helpers/NodeAnnotation.swift b/MeshtasticClient/Views/Helpers/NodeAnnotation.swift new file mode 100644 index 00000000..37050bdb --- /dev/null +++ b/MeshtasticClient/Views/Helpers/NodeAnnotation.swift @@ -0,0 +1,26 @@ +import Foundation +import SwiftUI + +struct NodeAnnotation: View { + let time: Date + + var body: some View { + + VStack(spacing: 0) { + Text(time, style: .offset) + .font(.callout).foregroundColor(.accentColor) + .padding(5) + .background(Color(.white)) + .cornerRadius(10) + + Image(systemName: "mappin.circle.fill") + .font(.title) + .foregroundColor(.red) + + Image(systemName: "arrowtriangle.down.fill") + .font(.caption) + .foregroundColor(.red) + .offset(x: 0, y: -5) + } + } +} diff --git a/MeshtasticClient/Views/Nodes/NodeDetail.swift b/MeshtasticClient/Views/Nodes/NodeDetail.swift index 5a52d5d3..0693cebb 100644 --- a/MeshtasticClient/Views/Nodes/NodeDetail.swift +++ b/MeshtasticClient/Views/Nodes/NodeDetail.swift @@ -56,7 +56,7 @@ struct NodeDetail: View { content: { - CircleText(text: node.user!.shortName ?? "???", color: .accentColor) + NodeAnnotation(time: location.time!) } ) } From 4847731402c2805e4656363b00907e1eb2882e53 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 28 May 2022 01:41:55 -0700 Subject: [PATCH 02/12] Fix lingering scanned but missing ble devices --- Meshtastic Client.xcodeproj/project.pbxproj | 4 ++-- MeshtasticClient/Helpers/BLEManager.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 3782365f..93d535d8 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -755,7 +755,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -787,7 +787,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 8; + CURRENT_PROJECT_VERSION = 9; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index b4e1fd2e..5de1c08d 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -195,8 +195,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } let today = Date() - let oneMinuteAgo = Calendar.current.date(byAdding: .minute, value: -1, to: today)! - peripherals.removeAll(where: { $0.lastUpdate <= oneMinuteAgo}) + let visibleDuration = Calendar.current.date(byAdding: .second, value: -5, to: today)! + peripherals.removeAll(where: { $0.lastUpdate <= visibleDuration}) } // Called when a peripheral is connected From 12da2fd6286332923b20e3fba5b64ec9fdf7c27e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 29 May 2022 22:02:25 -0700 Subject: [PATCH 03/12] Shutdown and Reboot buttons for nodes --- Meshtastic Client.xcodeproj/project.pbxproj | 4 +- MeshtasticClient/Helpers/BLEManager.swift | 41 +++++++++++- .../Views/Bluetooth/Connect.swift | 2 +- MeshtasticClient/Views/Nodes/NodeDetail.swift | 63 ++++++++++++++++++- 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 93d535d8..8279af3b 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -755,7 +755,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 10; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -787,7 +787,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 9; + CURRENT_PROJECT_VERSION = 10; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 5de1c08d..9e18b01e 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -968,15 +968,50 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } - // Send Position public func sendShutdown(destNum: Int64, wantResponse: Bool) -> Bool { var adminPacket = AdminMessage() - adminPacket.rebootSeconds = 30 + adminPacket.shutdownSeconds = 10 var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(connectedPeripheral.num) - meshPacket.from = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { + + var adminPacket = AdminMessage() + adminPacket.rebootSeconds = 10 + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Date: Mon, 30 May 2022 21:48:46 -0700 Subject: [PATCH 04/12] Restrict shutdown and reboot to connected node, update position to show unknown age for nodes older than six months --- Meshtastic Client.xcodeproj/project.pbxproj | 4 ++ .../xcshareddata/swiftpm/Package.resolved | 42 +++++++++---------- MeshtasticClient/Helpers/BLEManager.swift | 39 +---------------- .../Views/Helpers/DateTimeText.swift | 30 +++++++++++++ .../Views/Helpers/NodeAnnotation.swift | 6 +-- MeshtasticClient/Views/Nodes/NodeDetail.swift | 4 +- 6 files changed, 60 insertions(+), 65 deletions(-) create mode 100644 MeshtasticClient/Views/Helpers/DateTimeText.swift diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 8279af3b..3767c7dc 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; DDCA31322826009C00207175 /* PassKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDCA31312826009C00207175 /* PassKit.framework */; }; + DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; /* End PBXBuildFile section */ @@ -139,6 +140,7 @@ DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = ""; }; DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; DDCA31312826009C00207175 /* PassKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PassKit.framework; path = System/Library/Frameworks/PassKit.framework; sourceTree = SDKROOT; }; + DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -361,6 +363,7 @@ DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, DDC3B273283F411B00AC321C /* LastHeardText.swift */, DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */, + DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */, ); path = Helpers; sourceTree = ""; @@ -583,6 +586,7 @@ DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */, DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, + DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */, DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */, DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, diff --git a/Meshtastic Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6685f05b..bccfe8c9 100644 --- a/Meshtastic Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Meshtastic Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,25 +1,23 @@ { - "object": { - "pins": [ - { - "package": "SQLite.swift", - "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", - "state": { - "branch": null, - "revision": "60a65015f6402b7c34b9a924f755ca0a73afeeaa", - "version": "0.13.1" - } - }, - { - "package": "SwiftProtobuf", - "repositoryURL": "https://github.com/apple/swift-protobuf.git", - "state": { - "branch": null, - "revision": "e1499bc69b9040b29184f7f2996f7bab467c1639", - "version": "1.19.0" - } + "pins" : [ + { + "identity" : "sqlite.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stephencelis/SQLite.swift.git", + "state" : { + "revision" : "60a65015f6402b7c34b9a924f755ca0a73afeeaa", + "version" : "0.13.1" } - ] - }, - "version": 1 + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639", + "version" : "1.19.0" + } + } + ], + "version" : 2 } diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 9e18b01e..937a0c39 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -884,7 +884,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph var meshPacket = MeshPacket() meshPacket.to = UInt32(destNum) - meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.from = 0 // Send 0 as from from phone to device to avoid warning about client trying to set node num meshPacket.wantAck = wantResponse var dataMessage = DataMessage() @@ -933,41 +933,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } - // MARK: Device Settings - public func getSettings() -> Bool { - - var adminPacket = AdminMessage() - - var meshPacket: MeshPacket = MeshPacket() - meshPacket.to = UInt32(connectedPeripheral.num) - meshPacket.from = UInt32(connectedPeripheral.num) - meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Bool { var adminPacket = AdminMessage() @@ -1001,7 +966,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } return false - } public func sendReboot(destNum: Int64, wantResponse: Bool) -> Bool { @@ -1037,6 +1001,5 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } return false - } } diff --git a/MeshtasticClient/Views/Helpers/DateTimeText.swift b/MeshtasticClient/Views/Helpers/DateTimeText.swift new file mode 100644 index 00000000..523f6445 --- /dev/null +++ b/MeshtasticClient/Views/Helpers/DateTimeText.swift @@ -0,0 +1,30 @@ +// +// DateTimeText.swift +// MeshtasticClient +// +// Created by Garth Vander Houwen on 5/30/22. +// + +import SwiftUI +// +// LastHeardText.swift +// Meshtastic Apple +// +// Created by Garth Vander Houwen on 5/25/22. +// +struct DateTimeText: View { + var dateTime: Date? + + let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date()) + + var body: some View { + if (dateTime != nil && dateTime! >= sixMonthsAgo!){ + + Text("\(dateTime!, style: .date) \(dateTime!, style: .time)") + + } else { + + Text("Unknown Age") + } + } +} diff --git a/MeshtasticClient/Views/Helpers/NodeAnnotation.swift b/MeshtasticClient/Views/Helpers/NodeAnnotation.swift index 37050bdb..a8c47579 100644 --- a/MeshtasticClient/Views/Helpers/NodeAnnotation.swift +++ b/MeshtasticClient/Views/Helpers/NodeAnnotation.swift @@ -14,12 +14,12 @@ struct NodeAnnotation: View { .cornerRadius(10) Image(systemName: "mappin.circle.fill") - .font(.title) - .foregroundColor(.red) + .font(.largeTitle) + .foregroundColor(.accentColor) Image(systemName: "arrowtriangle.down.fill") .font(.caption) - .foregroundColor(.red) + .foregroundColor(.accentColor) .offset(x: 0, y: -5) } } diff --git a/MeshtasticClient/Views/Nodes/NodeDetail.swift b/MeshtasticClient/Views/Nodes/NodeDetail.swift index 5191d05b..db5a979e 100644 --- a/MeshtasticClient/Views/Nodes/NodeDetail.swift +++ b/MeshtasticClient/Views/Nodes/NodeDetail.swift @@ -85,7 +85,7 @@ struct NodeDetail: View { ScrollView { HStack { - if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num { + if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { Button(action: { @@ -315,7 +315,7 @@ struct NodeDetail: View { .symbolRenderingMode(.hierarchical) Text("Time:") .font(.caption) - Text("\(mappin.time!, style: .date) \(mappin.time!, style: .time)") + DateTimeText(dateTime: mappin.time) .foregroundColor(.gray) .font(.caption) Divider() From d3fd9ead544cf26ab2d275eebe216792368d8641 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 30 May 2022 21:51:37 -0700 Subject: [PATCH 05/12] Remove pass kit, wallet requires a server so is a bad fit for meshtastic, was able to generate a QR code without pass kit or a library --- Meshtastic Client.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 3767c7dc..45bc181d 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -58,7 +58,6 @@ DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; }; DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; - DDCA31322826009C00207175 /* PassKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDCA31312826009C00207175 /* PassKit.framework */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; /* End PBXBuildFile section */ @@ -149,7 +148,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DDCA31322826009C00207175 /* PassKit.framework in Frameworks */, C9697FA527933B8C00250207 /* SQLite in Frameworks */, DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */, ); From 82adcbd8de184a4d08c6bcf367b7bd7256e0dccb Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 1 Jun 2022 23:23:02 -0700 Subject: [PATCH 06/12] Finish refactoring Packet handling out of the blue manager Protobufs update --- Meshtastic Client.xcodeproj/project.pbxproj | 3 +- MeshtasticClient/Helpers/BLEManager.swift | 351 +++------------- MeshtasticClient/Helpers/MeshPackets.swift | 309 +++++++++++++- MeshtasticClient/Protobufs/apponly.pb.swift | 25 +- .../Protobufs/deviceonly.pb.swift | 393 ------------------ .../Views/Settings/ShareChannel.swift | 3 +- 6 files changed, 399 insertions(+), 685 deletions(-) diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 45bc181d..c8959e3b 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -460,7 +460,7 @@ TargetAttributes = { DDC2E15326CE248E0042C5E4 = { CreatedOnToolsVersion = 12.5.1; - LastSwiftMigration = 1320; + LastSwiftMigration = 1340; }; DDC2E16926CE248F0042C5E4 = { CreatedOnToolsVersion = 12.5.1; @@ -805,6 +805,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "MeshtasticClient/Compression/MeshtasticClient-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 937a0c39..ae9eb44d 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -299,6 +299,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // MARK: Discover Characteristics Event func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { + if let e = error { if meshLoggingEnabled { MeshLogger.log("🚫 BLE didDiscoverCharacteristicsFor error by \(peripheral.name ?? "Unknown") \(e)") } @@ -333,8 +334,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph default: break } - - } + } } func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { @@ -386,7 +386,41 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph switch decodedInfo.packet.decoded.portnum { case .unknownApp: - print("MyInfo or NodeInfo") + if decodedInfo.myInfo.myNodeNum != 0 { + + let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, meshLogging: meshLoggingEnabled, context: context!) + + if myInfo != nil { + + self.connectedPeripheral.bitrate = myInfo!.bitrate + self.connectedPeripheral.num = myInfo!.myNodeNum + lastConnnectionVersion = myInfo?.firmwareVersion ?? myInfo!.firmwareVersion ?? "Unknown" + self.connectedPeripheral.firmwareVersion = myInfo!.firmwareVersion ?? "Unknown" + self.connectedPeripheral.name = myInfo!.bleName ?? "Unknown" + } + + } else if decodedInfo.nodeInfo.num != 0 { + + let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, meshLogging: meshLoggingEnabled, context: context!) + + if nodeInfo != nil { + + self.connectedPeripheral.channelUtilization = decodedInfo.nodeInfo.deviceMetrics.channelUtilization + self.connectedPeripheral.airTime = decodedInfo.nodeInfo.deviceMetrics.airUtilTx + + if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo!.num { + + if nodeInfo!.user != nil { + + connectedPeripheral.name = nodeInfo!.user!.longName ?? "Unknown" + } + } + } + + } else { + + if meshLoggingEnabled { 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), meshLogging: meshLoggingEnabled, context: context!) case .remoteHardwareApp: @@ -394,7 +428,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph case .positionApp: positionPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) case .nodeinfoApp: - nodeInfoPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) + nodeInfoAppPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) case .routingApp: routingPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) case .adminApp: @@ -425,288 +459,40 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("MAX PORT NUM OF 511") } - // MARK: Incoming MyInfo Packet - if decodedInfo.myInfo.myNodeNum != 0 { - - let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.myInfo.myNodeNum)) - - do { - let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity] - // Not Found Insert - if fetchedMyInfo.isEmpty { - let myInfo = MyInfoEntity(context: context!) - myInfo.myNodeNum = Int64(decodedInfo.myInfo.myNodeNum) - myInfo.hasGps = decodedInfo.myInfo.hasGps_p - myInfo.bitrate = decodedInfo.myInfo.bitrate - self.connectedPeripheral.bitrate = myInfo.bitrate - - // Swift does strings weird, this does work to get the version without the github hash - let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".") - var version = decodedInfo.myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: decodedInfo.myInfo.firmwareVersion))] - version = version.dropLast() - myInfo.firmwareVersion = String(version) - lastConnnectionVersion = String(version) + // MARK: Check for an All / Broadcast User + let fetchBCUserRequest: NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") + fetchBCUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(broadcastNodeNum)) + + do { + let fetchedUser = try context?.fetch(fetchBCUserRequest) as! [UserEntity] - myInfo.messageTimeoutMsec = Int32(bitPattern: decodedInfo.myInfo.messageTimeoutMsec) - myInfo.minAppVersion = Int32(bitPattern: decodedInfo.myInfo.minAppVersion) - myInfo.maxChannels = Int32(bitPattern: decodedInfo.myInfo.maxChannels) - self.connectedPeripheral.num = myInfo.myNodeNum - self.connectedPeripheral.firmwareVersion = myInfo.firmwareVersion ?? "Unknown" - self.connectedPeripheral.name = myInfo.bleName ?? "Unknown" - - let fetchBCUserRequest: NSFetchRequest = NSFetchRequest.init(entityName: "UserEntity") - fetchBCUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.myInfo.myNodeNum)) - - do { - let fetchedUser = try context?.fetch(fetchBCUserRequest) as! [UserEntity] - - if fetchedUser.isEmpty { - // Save the broadcast user if it does not exist - let bcu: UserEntity = UserEntity(context: context!) - bcu.shortName = "ALL" - bcu.longName = "All - Broadcast" - bcu.hwModel = "UNSET" - bcu.num = Int64(broadcastNodeNum) - bcu.userId = "BROADCASTNODE" - print("πŸ’Ύ Saved the All - Broadcast User") - } - - } catch { - - print("πŸ’₯ Error Saving the All - Broadcast User") - } - - } else { - - fetchedMyInfo[0].myNodeNum = Int64(decodedInfo.myInfo.myNodeNum) - fetchedMyInfo[0].hasGps = decodedInfo.myInfo.hasGps_p - fetchedMyInfo[0].bitrate = decodedInfo.myInfo.bitrate - - let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".")//.lastIndex(of: ".", offsetBy: -1) - var version = decodedInfo.myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset:6, in: decodedInfo.myInfo.firmwareVersion))] - version = version.dropLast() - fetchedMyInfo[0].firmwareVersion = String(version) - lastConnnectionVersion = String(version) - fetchedMyInfo[0].messageTimeoutMsec = Int32(bitPattern: decodedInfo.myInfo.messageTimeoutMsec) - fetchedMyInfo[0].minAppVersion = Int32(bitPattern: decodedInfo.myInfo.minAppVersion) - fetchedMyInfo[0].maxChannels = Int32(bitPattern: decodedInfo.myInfo.maxChannels) - - self.connectedPeripheral.num = fetchedMyInfo[0].myNodeNum - self.connectedPeripheral.firmwareVersion = fetchedMyInfo[0].firmwareVersion ?? "Unknown" - self.connectedPeripheral.name = fetchedMyInfo[0].bleName ?? "Unknown" - self.connectedPeripheral.bitrate = fetchedMyInfo[0].bitrate - - } - do { - - try context!.save() - if meshLoggingEnabled { MeshLogger.log("πŸ’Ύ Saved a myInfo for \(peripheral.name ?? String(decodedInfo.myInfo.myNodeNum))") } - - } catch { - - context!.rollback() - - let nsError = error as NSError - print("πŸ’₯ Error Saving Core Data MyInfoEntity: \(nsError)") - } - - } catch { - - print("πŸ’₯ Fetch MyInfo Error") + if fetchedUser.isEmpty { + // Save the broadcast user if it does not exist + let bcu: UserEntity = UserEntity(context: context!) + bcu.shortName = "ALL" + bcu.longName = "All - Broadcast" + bcu.hwModel = "UNSET" + bcu.num = Int64(broadcastNodeNum) + bcu.userId = "BROADCASTNODE" + print("πŸ’Ύ Saved the All - Broadcast User") } - // MARK: Share Location Position Update Timer - // Use context to pass the radio name with the timer - // Use a RunLoop to prevent the timer from running on the main UI thread - if userSettings?.provideLocation ?? false { - - if self.positionTimer != nil { - self.positionTimer!.invalidate() - } - let context = ["name": "@\(peripheral.name ?? "Unknown")"] - self.positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) - RunLoop.current.add(self.positionTimer!, forMode: .common) - } + } catch { + + print("πŸ’₯ Error Saving the All - Broadcast User") } - // MARK: Incoming Node Info Packet - if decodedInfo.nodeInfo.num != 0 { - - let fetchNodeRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") - fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.nodeInfo.num)) - - do { - - let fetchedNode = try context?.fetch(fetchNodeRequest) as! [NodeInfoEntity] - // Not Found Insert - if fetchedNode.isEmpty && decodedInfo.nodeInfo.hasUser { - - let newNode = NodeInfoEntity(context: context!) - newNode.id = Int64(decodedInfo.nodeInfo.num) - newNode.num = Int64(decodedInfo.nodeInfo.num) - - if decodedInfo.nodeInfo.hasDeviceMetrics { - - let telemetry = TelemetryEntity(context: context!) - - telemetry.batteryLevel = Int32(decodedInfo.nodeInfo.deviceMetrics.batteryLevel) - telemetry.voltage = decodedInfo.nodeInfo.deviceMetrics.voltage - telemetry.channelUtilization = decodedInfo.nodeInfo.deviceMetrics.channelUtilization - self.connectedPeripheral.channelUtilization = telemetry.channelUtilization - telemetry.airUtilTx = decodedInfo.nodeInfo.deviceMetrics.airUtilTx - self.connectedPeripheral.airTime = decodedInfo.nodeInfo.deviceMetrics.airUtilTx - - var newTelemetries = [TelemetryEntity]() - newTelemetries.append(telemetry) - newNode.telemetries? = NSOrderedSet(array: newTelemetries) - } - - newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.lastHeard))) - newNode.snr = decodedInfo.nodeInfo.snr - - if self.connectedPeripheral != nil && self.connectedPeripheral.num == newNode.num { - - if decodedInfo.nodeInfo.hasUser { - - connectedPeripheral.name = decodedInfo.nodeInfo.user.longName - } - } - - if decodedInfo.nodeInfo.hasUser { - - let newUser = UserEntity(context: context!) - newUser.userId = decodedInfo.nodeInfo.user.id - newUser.num = Int64(decodedInfo.nodeInfo.num) - newUser.longName = decodedInfo.nodeInfo.user.longName - newUser.shortName = decodedInfo.nodeInfo.user.shortName - newUser.macaddr = decodedInfo.nodeInfo.user.macaddr - newUser.hwModel = String(describing: decodedInfo.nodeInfo.user.hwModel).uppercased() - newNode.user = newUser - } - - let position = PositionEntity(context: context!) - position.latitudeI = decodedInfo.nodeInfo.position.latitudeI - position.longitudeI = decodedInfo.nodeInfo.position.longitudeI - position.altitude = decodedInfo.nodeInfo.position.altitude - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.position.time))) - - var newPostions = [PositionEntity]() - newPostions.append(position) - newNode.positions? = NSOrderedSet(array: newPostions) - - // Look for a MyInfo - let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.nodeInfo.num)) - - do { - - let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity] - if fetchedMyInfo.count > 0 { - newNode.myInfo = fetchedMyInfo[0] - - } - - } catch { - print("πŸ’₯ Fetch MyInfo Error") - } - - } else if decodedInfo.nodeInfo.hasUser && decodedInfo.nodeInfo.num > 0 { - - fetchedNode[0].id = Int64(decodedInfo.nodeInfo.num) - fetchedNode[0].num = Int64(decodedInfo.nodeInfo.num) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.lastHeard))) - fetchedNode[0].snr = decodedInfo.nodeInfo.snr - - if self.connectedPeripheral != nil && self.connectedPeripheral.num == fetchedNode[0].num { - - if decodedInfo.nodeInfo.hasUser { - - self.connectedPeripheral.name = fetchedNode[0].user!.longName ?? "Unknown" - } - } - - if decodedInfo.nodeInfo.hasUser { - - fetchedNode[0].user!.userId = decodedInfo.nodeInfo.user.id - fetchedNode[0].user!.num = Int64(decodedInfo.nodeInfo.num) - fetchedNode[0].user!.longName = decodedInfo.nodeInfo.user.longName - fetchedNode[0].user!.shortName = decodedInfo.nodeInfo.user.shortName - fetchedNode[0].user!.macaddr = decodedInfo.nodeInfo.user.macaddr - fetchedNode[0].user!.hwModel = String(describing: decodedInfo.nodeInfo.user.hwModel).uppercased() - } - - if decodedInfo.nodeInfo.hasDeviceMetrics { - - let newTelemetry = TelemetryEntity(context: context!) - - newTelemetry.batteryLevel = Int32(decodedInfo.nodeInfo.deviceMetrics.batteryLevel) - newTelemetry.voltage = decodedInfo.nodeInfo.deviceMetrics.voltage - newTelemetry.channelUtilization = decodedInfo.nodeInfo.deviceMetrics.channelUtilization - self.connectedPeripheral.channelUtilization = newTelemetry.channelUtilization - newTelemetry.airUtilTx = decodedInfo.nodeInfo.deviceMetrics.airUtilTx - self.connectedPeripheral.airTime = decodedInfo.nodeInfo.deviceMetrics.airUtilTx - - let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as! NSMutableOrderedSet - fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet - } - - if decodedInfo.nodeInfo.hasPosition { - - let position = PositionEntity(context: context!) - position.latitudeI = decodedInfo.nodeInfo.position.latitudeI - position.longitudeI = decodedInfo.nodeInfo.position.longitudeI - position.altitude = decodedInfo.nodeInfo.position.altitude - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.position.time))) - - let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet - - fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet - - } - - // Look for a MyInfo - let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") - fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.nodeInfo.num)) - - do { - - let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity] - if fetchedMyInfo.count > 0 { - - fetchedNode[0].myInfo = fetchedMyInfo[0] - } - - } catch { - print("πŸ’₯ Fetch MyInfo Error") - } - } - do { - - try context!.save() - print("πŸ’Ύ Saved a nodeInfo for \(decodedInfo.nodeInfo.num)") - - } catch { - - context!.rollback() - - let nsError = error as NSError - print("πŸ’₯ Error Saving Core Data NodeInfoEntity: \(nsError)") - } - - } catch { - - print("πŸ’₯ Fetch NodeInfoEntity Error") - } - - if decodedInfo.nodeInfo.hasUser { - - if meshLoggingEnabled { MeshLogger.log("πŸ’Ύ BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") } - - } else { - - if meshLoggingEnabled { MeshLogger.log("πŸ’Ύ BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.num)") } + // MARK: Share Location Position Update Timer + // Use context to pass the radio name with the timer + // Use a RunLoop to prevent the timer from running on the main UI thread + if userSettings?.provideLocation ?? false { + + if self.positionTimer != nil { + self.positionTimer!.invalidate() } + let context = ["name": "@\(peripheral.name ?? "Unknown")"] + self.positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true) + RunLoop.current.add(self.positionTimer!, forMode: .common) } if decodedInfo.configCompleteID != 0 { @@ -720,7 +506,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph case FROMNUM_UUID : - print("πŸ—žοΈ FROMNUM Notification, value will be read below") + print("πŸ—žοΈ BLE FROMNUM (Notify) characteristic, value will be read next") default: @@ -731,7 +517,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph peripheral.readValue(for: FROMRADIO_characteristic) } - // Send Message public func sendMessage(message: String, toUserNum: Int64, isEmoji: Bool, replyID: Int64) -> Bool { var success = false @@ -854,7 +639,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return success } - // Send Position public func sendPosition(destNum: Int64, wantResponse: Bool) -> Bool { var success = false @@ -1002,4 +786,5 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + } diff --git a/MeshtasticClient/Helpers/MeshPackets.swift b/MeshtasticClient/Helpers/MeshPackets.swift index f2cf5c50..3b7f00f6 100644 --- a/MeshtasticClient/Helpers/MeshPackets.swift +++ b/MeshtasticClient/Helpers/MeshPackets.swift @@ -8,7 +8,267 @@ import Foundation import CoreData -func nodeInfoPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { +func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> MyInfoEntity? { + + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum)) + + do { + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity] + // Not Found Insert + if fetchedMyInfo.isEmpty { + + let myInfoEntity = MyInfoEntity(context: context) + myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum) + myInfoEntity.hasGps = myInfo.hasGps_p + myInfoEntity.hasWifi = myInfo.hasWifi_p + myInfoEntity.bitrate = myInfo.bitrate + + // Swift does strings weird, this does work to get the version without the github hash + let lastDotIndex = myInfo.firmwareVersion.lastIndex(of: ".") + var version = myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: myInfo.firmwareVersion))] + version = version.dropLast() + myInfoEntity.firmwareVersion = String(version) + myInfoEntity.messageTimeoutMsec = Int32(bitPattern: myInfo.messageTimeoutMsec) + myInfoEntity.minAppVersion = Int32(bitPattern: myInfo.minAppVersion) + myInfoEntity.maxChannels = Int32(bitPattern: myInfo.maxChannels) + + do { + + try context.save() + if meshLogging { MeshLogger.log("πŸ’Ύ 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].myNodeNum = Int64(myInfo.myNodeNum) + fetchedMyInfo[0].hasGps = myInfo.hasGps_p + fetchedMyInfo[0].bitrate = myInfo.bitrate + + let lastDotIndex = myInfo.firmwareVersion.lastIndex(of: ".")//.lastIndex(of: ".", offsetBy: -1) + var version = myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset:6, in: myInfo.firmwareVersion))] + version = version.dropLast() + fetchedMyInfo[0].firmwareVersion = String(version) + fetchedMyInfo[0].messageTimeoutMsec = Int32(bitPattern: myInfo.messageTimeoutMsec) + fetchedMyInfo[0].minAppVersion = Int32(bitPattern: myInfo.minAppVersion) + fetchedMyInfo[0].maxChannels = Int32(bitPattern: myInfo.maxChannels) + + do { + + try context.save() + if meshLogging { MeshLogger.log("πŸ’Ύ Updated myInfo for node number: \(String(myInfo.myNodeNum))") } + return fetchedMyInfo[0] + + } catch { + + context.rollback() + + let nsError = error as NSError + print("πŸ’₯ Error Updating Core Data MyInfoEntity: \(nsError)") + } + } + + } catch { + + print("πŸ’₯ Fetch MyInfo Error") + } + return nil +} + + +func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> NodeInfoEntity? { + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num)) + + do { + + let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity] + // Not Found Insert + if fetchedNode.isEmpty && nodeInfo.hasUser { + + let newNode = NodeInfoEntity(context: context) + newNode.id = Int64(nodeInfo.num) + newNode.num = Int64(nodeInfo.num) + + 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) + } + + 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) + newUser.longName = nodeInfo.user.longName + newUser.shortName = nodeInfo.user.shortName + newUser.macaddr = nodeInfo.user.macaddr + newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() + newNode.user = newUser + } + + let position = PositionEntity(context: context) + position.latitudeI = nodeInfo.position.latitudeI + position.longitudeI = nodeInfo.position.longitudeI + position.altitude = nodeInfo.position.altitude + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) + + var newPostions = [PositionEntity]() + newPostions.append(position) + newNode.positions? = NSOrderedSet(array: newPostions) + + // Look for a MyInfo + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num)) + + do { + + let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity] + if fetchedMyInfo.count > 0 { + newNode.myInfo = fetchedMyInfo[0] + } + + do { + + try context.save() + + if nodeInfo.hasUser { + + if meshLogging { MeshLogger.log("πŸ’Ύ BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.user.longName)") } + + } else { + + if meshLogging { MeshLogger.log("πŸ’Ύ BLE FROMRADIO received and nodeInfo inserted 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) + fetchedNode[0].num = Int64(nodeInfo.num) + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard))) + fetchedNode[0].snr = nodeInfo.snr + + + if nodeInfo.hasUser { + + fetchedNode[0].user!.userId = nodeInfo.user.id + fetchedNode[0].user!.num = Int64(nodeInfo.num) + fetchedNode[0].user!.longName = nodeInfo.user.longName + fetchedNode[0].user!.shortName = nodeInfo.user.shortName + fetchedNode[0].user!.macaddr = nodeInfo.user.macaddr + fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased() + } + + 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 + } + + if nodeInfo.hasPosition { + + let position = PositionEntity(context: context) + position.latitudeI = nodeInfo.position.latitudeI + position.longitudeI = nodeInfo.position.longitudeI + position.altitude = nodeInfo.position.altitude + 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 + let fetchMyInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MyInfoEntity") + 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 { + + if meshLogging { MeshLogger.log("πŸ’Ύ BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.user.longName)") } + + } else { + + if meshLogging { MeshLogger.log("πŸ’Ύ BLE FROMRADIO received and nodeInfo inserted 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 +} + +func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { let fetchNodeInfoAppRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) @@ -144,8 +404,6 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj if routingMessage.errorReason == Routing.Error.none { - print("Priority ACK no Error") - let fetchMessageRequest: NSFetchRequest = NSFetchRequest.init(entityName: "MessageEntity") fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID)) @@ -184,9 +442,48 @@ func telemetryPacket(packet: MeshPacket, meshLogging: Bool, context: NSManagedOb let telemetry = TelemetryEntity(context: context) - - - if meshLogging { MeshLogger.log("ℹ️ MESH PACKET received for Telemetry App UNHANDLED \(telemetryMessage)") } + let fetchNodeTelemetryRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + + let fetchedNode = try context.fetch(fetchNodeTelemetryRequest) as! [NodeInfoEntity] + + if fetchedNode.count == 1 { + + // Device Metrics + telemetry.airUtilTx = telemetryMessage.deviceMetrics.airUtilTx + telemetry.channelUtilization = telemetryMessage.deviceMetrics.channelUtilization + telemetry.batteryLevel = Int32(telemetryMessage.deviceMetrics.batteryLevel) + telemetry.voltage = telemetryMessage.deviceMetrics.voltage + + // Environment Metrics + telemetry.barometricPressure = telemetryMessage.environmentMetrics.barometricPressure + telemetry.current = telemetryMessage.environmentMetrics.current + telemetry.gasResistance = telemetryMessage.environmentMetrics.gasResistance + telemetry.relativeHumidity = telemetryMessage.environmentMetrics.relativeHumidity + telemetry.temperature = telemetryMessage.environmentMetrics.temperature + let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as! NSMutableOrderedSet + mutableTelemetries.add(telemetry) + + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(telemetryMessage.time))) + fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet + fetchedNode[0].objectWillChange.send() + } + + try context.save() + + if meshLogging { + MeshLogger.log("πŸ’Ύ Telemetry Saved for Node: \(packet.from)") + } + + } catch { + + context.rollback() + + let nsError = error as NSError + print("πŸ’₯ Error Saving Telemetry for Node \(packet.from) Error: \(nsError)") + } } else { diff --git a/MeshtasticClient/Protobufs/apponly.pb.swift b/MeshtasticClient/Protobufs/apponly.pb.swift index 687030b7..46ab82c8 100644 --- a/MeshtasticClient/Protobufs/apponly.pb.swift +++ b/MeshtasticClient/Protobufs/apponly.pb.swift @@ -32,12 +32,25 @@ struct ChannelSet { // methods supported on all messages. /// - /// TODO: REPLACE + /// Channel list with settings var settings: [ChannelSettings] = [] + /// + /// LoRa config + var loraConfig: Config.LoRaConfig { + get {return _loraConfig ?? Config.LoRaConfig()} + set {_loraConfig = newValue} + } + /// Returns true if `loraConfig` has been explicitly set. + var hasLoraConfig: Bool {return self._loraConfig != nil} + /// Clears the value of `loraConfig`. Subsequent reads from it will return its default value. + mutating func clearLoraConfig() {self._loraConfig = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} + + fileprivate var _loraConfig: Config.LoRaConfig? = nil } #if swift(>=5.5) && canImport(_Concurrency) @@ -50,6 +63,7 @@ extension ChannelSet: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio static let protoMessageName: String = "ChannelSet" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "settings"), + 2: .standard(proto: "lora_config"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -59,20 +73,29 @@ extension ChannelSet: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeRepeatedMessageField(value: &self.settings) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._loraConfig) }() default: break } } } func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if !self.settings.isEmpty { try visitor.visitRepeatedMessageField(value: self.settings, fieldNumber: 1) } + try { if let v = self._loraConfig { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: ChannelSet, rhs: ChannelSet) -> Bool { if lhs.settings != rhs.settings {return false} + if lhs._loraConfig != rhs._loraConfig {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/MeshtasticClient/Protobufs/deviceonly.pb.swift b/MeshtasticClient/Protobufs/deviceonly.pb.swift index 95139c11..52a31d33 100644 --- a/MeshtasticClient/Protobufs/deviceonly.pb.swift +++ b/MeshtasticClient/Protobufs/deviceonly.pb.swift @@ -215,180 +215,11 @@ struct OEMStore { init() {} } -struct LocalConfig { - // 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. - - /// - /// TODO: REPLACE - var device: Config.DeviceConfig { - get {return _storage._device ?? Config.DeviceConfig()} - set {_uniqueStorage()._device = newValue} - } - /// Returns true if `device` has been explicitly set. - var hasDevice: Bool {return _storage._device != nil} - /// Clears the value of `device`. Subsequent reads from it will return its default value. - mutating func clearDevice() {_uniqueStorage()._device = nil} - - /// - /// TODO: REPLACE - var position: Config.PositionConfig { - get {return _storage._position ?? Config.PositionConfig()} - set {_uniqueStorage()._position = newValue} - } - /// Returns true if `position` has been explicitly set. - var hasPosition: Bool {return _storage._position != nil} - /// Clears the value of `position`. Subsequent reads from it will return its default value. - mutating func clearPosition() {_uniqueStorage()._position = nil} - - /// - /// TODO: REPLACE - var power: Config.PowerConfig { - get {return _storage._power ?? Config.PowerConfig()} - set {_uniqueStorage()._power = newValue} - } - /// Returns true if `power` has been explicitly set. - var hasPower: Bool {return _storage._power != nil} - /// Clears the value of `power`. Subsequent reads from it will return its default value. - mutating func clearPower() {_uniqueStorage()._power = nil} - - /// - /// TODO: REPLACE - var wifi: Config.WiFiConfig { - get {return _storage._wifi ?? Config.WiFiConfig()} - set {_uniqueStorage()._wifi = newValue} - } - /// Returns true if `wifi` has been explicitly set. - var hasWifi: Bool {return _storage._wifi != nil} - /// Clears the value of `wifi`. Subsequent reads from it will return its default value. - mutating func clearWifi() {_uniqueStorage()._wifi = nil} - - /// - /// TODO: REPLACE - var display: Config.DisplayConfig { - get {return _storage._display ?? Config.DisplayConfig()} - set {_uniqueStorage()._display = newValue} - } - /// Returns true if `display` has been explicitly set. - var hasDisplay: Bool {return _storage._display != nil} - /// Clears the value of `display`. Subsequent reads from it will return its default value. - mutating func clearDisplay() {_uniqueStorage()._display = nil} - - /// - /// TODO: REPLACE - var lora: Config.LoRaConfig { - get {return _storage._lora ?? Config.LoRaConfig()} - set {_uniqueStorage()._lora = newValue} - } - /// Returns true if `lora` has been explicitly set. - var hasLora: Bool {return _storage._lora != nil} - /// Clears the value of `lora`. Subsequent reads from it will return its default value. - mutating func clearLora() {_uniqueStorage()._lora = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _storage = _StorageClass.defaultInstance -} - -struct LocalModuleConfig { - // 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. - - /// - /// TODO: REPLACE - var mqtt: ModuleConfig.MQTTConfig { - get {return _storage._mqtt ?? ModuleConfig.MQTTConfig()} - set {_uniqueStorage()._mqtt = newValue} - } - /// Returns true if `mqtt` has been explicitly set. - var hasMqtt: Bool {return _storage._mqtt != nil} - /// Clears the value of `mqtt`. Subsequent reads from it will return its default value. - mutating func clearMqtt() {_uniqueStorage()._mqtt = nil} - - /// - /// TODO: REPLACE - var serial: ModuleConfig.SerialConfig { - get {return _storage._serial ?? ModuleConfig.SerialConfig()} - set {_uniqueStorage()._serial = newValue} - } - /// Returns true if `serial` has been explicitly set. - var hasSerial: Bool {return _storage._serial != nil} - /// Clears the value of `serial`. Subsequent reads from it will return its default value. - mutating func clearSerial() {_uniqueStorage()._serial = nil} - - /// - /// TODO: REPLACE - var externalNotification: ModuleConfig.ExternalNotificationConfig { - get {return _storage._externalNotification ?? ModuleConfig.ExternalNotificationConfig()} - set {_uniqueStorage()._externalNotification = newValue} - } - /// Returns true if `externalNotification` has been explicitly set. - var hasExternalNotification: Bool {return _storage._externalNotification != nil} - /// Clears the value of `externalNotification`. Subsequent reads from it will return its default value. - mutating func clearExternalNotification() {_uniqueStorage()._externalNotification = nil} - - /// - /// TODO: REPLACE - var storeForward: ModuleConfig.StoreForwardConfig { - get {return _storage._storeForward ?? ModuleConfig.StoreForwardConfig()} - set {_uniqueStorage()._storeForward = newValue} - } - /// Returns true if `storeForward` has been explicitly set. - var hasStoreForward: Bool {return _storage._storeForward != nil} - /// Clears the value of `storeForward`. Subsequent reads from it will return its default value. - mutating func clearStoreForward() {_uniqueStorage()._storeForward = nil} - - /// - /// TODO: REPLACE - var rangeTest: ModuleConfig.RangeTestConfig { - get {return _storage._rangeTest ?? ModuleConfig.RangeTestConfig()} - set {_uniqueStorage()._rangeTest = newValue} - } - /// Returns true if `rangeTest` has been explicitly set. - var hasRangeTest: Bool {return _storage._rangeTest != nil} - /// Clears the value of `rangeTest`. Subsequent reads from it will return its default value. - mutating func clearRangeTest() {_uniqueStorage()._rangeTest = nil} - - /// - /// TODO: REPLACE - var telemetry: ModuleConfig.TelemetryConfig { - get {return _storage._telemetry ?? ModuleConfig.TelemetryConfig()} - set {_uniqueStorage()._telemetry = newValue} - } - /// Returns true if `telemetry` has been explicitly set. - var hasTelemetry: Bool {return _storage._telemetry != nil} - /// Clears the value of `telemetry`. Subsequent reads from it will return its default value. - mutating func clearTelemetry() {_uniqueStorage()._telemetry = nil} - - /// - /// TODO: REPLACE - var cannedMessage: ModuleConfig.CannedMessageConfig { - get {return _storage._cannedMessage ?? ModuleConfig.CannedMessageConfig()} - set {_uniqueStorage()._cannedMessage = newValue} - } - /// Returns true if `cannedMessage` has been explicitly set. - var hasCannedMessage: Bool {return _storage._cannedMessage != nil} - /// Clears the value of `cannedMessage`. Subsequent reads from it will return its default value. - mutating func clearCannedMessage() {_uniqueStorage()._cannedMessage = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _storage = _StorageClass.defaultInstance -} - #if swift(>=5.5) && canImport(_Concurrency) extension ScreenFonts: @unchecked Sendable {} extension DeviceState: @unchecked Sendable {} extension ChannelFile: @unchecked Sendable {} extension OEMStore: @unchecked Sendable {} -extension LocalConfig: @unchecked Sendable {} -extension LocalModuleConfig: @unchecked Sendable {} #endif // swift(>=5.5) && canImport(_Concurrency) // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -612,227 +443,3 @@ extension OEMStore: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB return true } } - -extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "LocalConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "device"), - 2: .same(proto: "position"), - 3: .same(proto: "power"), - 4: .same(proto: "wifi"), - 5: .same(proto: "display"), - 6: .same(proto: "lora"), - ] - - fileprivate class _StorageClass { - var _device: Config.DeviceConfig? = nil - var _position: Config.PositionConfig? = nil - var _power: Config.PowerConfig? = nil - var _wifi: Config.WiFiConfig? = nil - var _display: Config.DisplayConfig? = nil - var _lora: Config.LoRaConfig? = nil - - static let defaultInstance = _StorageClass() - - private init() {} - - init(copying source: _StorageClass) { - _device = source._device - _position = source._position - _power = source._power - _wifi = source._wifi - _display = source._display - _lora = source._lora - } - } - - fileprivate mutating func _uniqueStorage() -> _StorageClass { - if !isKnownUniquelyReferenced(&_storage) { - _storage = _StorageClass(copying: _storage) - } - return _storage - } - - mutating func decodeMessage(decoder: inout D) throws { - _ = _uniqueStorage() - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &_storage._device) }() - case 2: try { try decoder.decodeSingularMessageField(value: &_storage._position) }() - case 3: try { try decoder.decodeSingularMessageField(value: &_storage._power) }() - case 4: try { try decoder.decodeSingularMessageField(value: &_storage._wifi) }() - case 5: try { try decoder.decodeSingularMessageField(value: &_storage._display) }() - case 6: try { try decoder.decodeSingularMessageField(value: &_storage._lora) }() - default: break - } - } - } - } - - func traverse(visitor: inout V) throws { - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = _storage._device { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = _storage._position { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = _storage._power { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = _storage._wifi { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try { if let v = _storage._display { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - try { if let v = _storage._lora { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: LocalConfig, rhs: LocalConfig) -> Bool { - if lhs._storage !== rhs._storage { - let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in - let _storage = _args.0 - let rhs_storage = _args.1 - if _storage._device != rhs_storage._device {return false} - if _storage._position != rhs_storage._position {return false} - if _storage._power != rhs_storage._power {return false} - if _storage._wifi != rhs_storage._wifi {return false} - if _storage._display != rhs_storage._display {return false} - if _storage._lora != rhs_storage._lora {return false} - return true - } - if !storagesAreEqual {return false} - } - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "LocalModuleConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "mqtt"), - 2: .same(proto: "serial"), - 3: .standard(proto: "external_notification"), - 4: .standard(proto: "store_forward"), - 5: .standard(proto: "range_test"), - 6: .same(proto: "telemetry"), - 7: .standard(proto: "canned_message"), - ] - - fileprivate class _StorageClass { - var _mqtt: ModuleConfig.MQTTConfig? = nil - var _serial: ModuleConfig.SerialConfig? = nil - var _externalNotification: ModuleConfig.ExternalNotificationConfig? = nil - var _storeForward: ModuleConfig.StoreForwardConfig? = nil - var _rangeTest: ModuleConfig.RangeTestConfig? = nil - var _telemetry: ModuleConfig.TelemetryConfig? = nil - var _cannedMessage: ModuleConfig.CannedMessageConfig? = nil - - static let defaultInstance = _StorageClass() - - private init() {} - - init(copying source: _StorageClass) { - _mqtt = source._mqtt - _serial = source._serial - _externalNotification = source._externalNotification - _storeForward = source._storeForward - _rangeTest = source._rangeTest - _telemetry = source._telemetry - _cannedMessage = source._cannedMessage - } - } - - fileprivate mutating func _uniqueStorage() -> _StorageClass { - if !isKnownUniquelyReferenced(&_storage) { - _storage = _StorageClass(copying: _storage) - } - return _storage - } - - mutating func decodeMessage(decoder: inout D) throws { - _ = _uniqueStorage() - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &_storage._mqtt) }() - case 2: try { try decoder.decodeSingularMessageField(value: &_storage._serial) }() - case 3: try { try decoder.decodeSingularMessageField(value: &_storage._externalNotification) }() - case 4: try { try decoder.decodeSingularMessageField(value: &_storage._storeForward) }() - case 5: try { try decoder.decodeSingularMessageField(value: &_storage._rangeTest) }() - case 6: try { try decoder.decodeSingularMessageField(value: &_storage._telemetry) }() - case 7: try { try decoder.decodeSingularMessageField(value: &_storage._cannedMessage) }() - default: break - } - } - } - } - - func traverse(visitor: inout V) throws { - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = _storage._mqtt { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = _storage._serial { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = _storage._externalNotification { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = _storage._storeForward { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try { if let v = _storage._rangeTest { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - try { if let v = _storage._telemetry { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - try { if let v = _storage._cannedMessage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: LocalModuleConfig, rhs: LocalModuleConfig) -> Bool { - if lhs._storage !== rhs._storage { - let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in - let _storage = _args.0 - let rhs_storage = _args.1 - if _storage._mqtt != rhs_storage._mqtt {return false} - if _storage._serial != rhs_storage._serial {return false} - if _storage._externalNotification != rhs_storage._externalNotification {return false} - if _storage._storeForward != rhs_storage._storeForward {return false} - if _storage._rangeTest != rhs_storage._rangeTest {return false} - if _storage._telemetry != rhs_storage._telemetry {return false} - if _storage._cannedMessage != rhs_storage._cannedMessage {return false} - return true - } - if !storagesAreEqual {return false} - } - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/MeshtasticClient/Views/Settings/ShareChannel.swift b/MeshtasticClient/Views/Settings/ShareChannel.swift index 1756d719..1e252d7a 100644 --- a/MeshtasticClient/Views/Settings/ShareChannel.swift +++ b/MeshtasticClient/Views/Settings/ShareChannel.swift @@ -36,8 +36,9 @@ struct ShareChannel: View { @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings + let channelSet = ChannelSet() - @State private var text = "meshtastic.org" + @State private var text = "https://www.meshtastic.org/e/#" var qrCodeImage = QrCodeImage() var body: some View { From 59fe6f9ec504c29fd0c44796c646f808ecebc2df Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 1 Jun 2022 23:33:12 -0700 Subject: [PATCH 07/12] Remove stray print statements --- MeshtasticClient/Helpers/MeshPackets.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/MeshtasticClient/Helpers/MeshPackets.swift b/MeshtasticClient/Helpers/MeshPackets.swift index 3b7f00f6..9acc2b9c 100644 --- a/MeshtasticClient/Helpers/MeshPackets.swift +++ b/MeshtasticClient/Helpers/MeshPackets.swift @@ -365,8 +365,6 @@ func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { if let routingMessage = try? Routing(serializedData: packet.decoded.payload) { - print(packet.decoded.requestID) - print(routingMessage) let error = routingMessage.errorReason @@ -397,7 +395,7 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj 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: - print(error) + break } if meshLogging { MeshLogger.log("πŸ•ΈοΈ ROUTING PACKET received for RequestID: \(packet.decoded.requestID) Error: \(errorExplanation)") } From e9e39e3c0c45905f4bb081fcdd9c1b46b038be81 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 3 Jun 2022 18:13:31 -0700 Subject: [PATCH 08/12] Update Protobufs --- MeshtasticClient/Helpers/MeshPackets.swift | 23 ++++++++++++++++---- MeshtasticClient/Protobufs/portnums.pb.swift | 8 +++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/MeshtasticClient/Helpers/MeshPackets.swift b/MeshtasticClient/Helpers/MeshPackets.swift index 9acc2b9c..3ed4edad 100644 --- a/MeshtasticClient/Helpers/MeshPackets.swift +++ b/MeshtasticClient/Helpers/MeshPackets.swift @@ -82,7 +82,6 @@ func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObje } return nil } - func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> NodeInfoEntity? { @@ -282,7 +281,24 @@ func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManage fetchedNode[0].num = Int64(packet.from) fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime))) fetchedNode[0].snr = packet.rxSnr - + + 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) + } + } + } else { return } @@ -394,8 +410,7 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj 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: - break + default: () } if meshLogging { MeshLogger.log("πŸ•ΈοΈ ROUTING PACKET received for RequestID: \(packet.decoded.requestID) Error: \(errorExplanation)") } diff --git a/MeshtasticClient/Protobufs/portnums.pb.swift b/MeshtasticClient/Protobufs/portnums.pb.swift index 1f63debc..10cef65e 100644 --- a/MeshtasticClient/Protobufs/portnums.pb.swift +++ b/MeshtasticClient/Protobufs/portnums.pb.swift @@ -77,6 +77,10 @@ enum PortNum: SwiftProtobuf.Enum { /// Compressed TEXT_MESSAGE payloads. case textMessageCompressedApp // = 7 + /// + /// Waypoint payloads. + case waypointApp // = 8 + /// /// Provides a 'ping' service that replies to any packet it receives. /// Also serves as a small example module. @@ -143,6 +147,7 @@ enum PortNum: SwiftProtobuf.Enum { case 5: self = .routingApp case 6: self = .adminApp case 7: self = .textMessageCompressedApp + case 8: self = .waypointApp case 32: self = .replyApp case 33: self = .ipTunnelApp case 64: self = .serialApp @@ -167,6 +172,7 @@ enum PortNum: SwiftProtobuf.Enum { case .routingApp: return 5 case .adminApp: return 6 case .textMessageCompressedApp: return 7 + case .waypointApp: return 8 case .replyApp: return 32 case .ipTunnelApp: return 33 case .serialApp: return 64 @@ -196,6 +202,7 @@ extension PortNum: CaseIterable { .routingApp, .adminApp, .textMessageCompressedApp, + .waypointApp, .replyApp, .ipTunnelApp, .serialApp, @@ -227,6 +234,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding { 5: .same(proto: "ROUTING_APP"), 6: .same(proto: "ADMIN_APP"), 7: .same(proto: "TEXT_MESSAGE_COMPRESSED_APP"), + 8: .same(proto: "WAYPOINT_APP"), 32: .same(proto: "REPLY_APP"), 33: .same(proto: "IP_TUNNEL_APP"), 64: .same(proto: "SERIAL_APP"), From f4c4df81821289de56ee596dca20313065cd762f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 4 Jun 2022 07:41:52 -0700 Subject: [PATCH 09/12] Fix message view rendering issues for acts and incoming messages --- Meshtastic Client.xcodeproj/project.pbxproj | 4 +++ MeshtasticClient/Helpers/BLEManager.swift | 2 ++ MeshtasticClient/Helpers/MeshPackets.swift | 2 ++ .../Persistence/UserEntityExtension.swift | 16 ++++++++++++ MeshtasticClient/Protobufs/config.pb.swift | 26 ------------------- .../Views/Messages/UserMessageList.swift | 6 ++--- 6 files changed, 26 insertions(+), 30 deletions(-) create mode 100644 MeshtasticClient/Persistence/UserEntityExtension.swift diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index c8959e3b..2a7c36dd 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; }; DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; + DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; /* End PBXBuildFile section */ @@ -140,6 +141,7 @@ DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; DDCA31312826009C00207175 /* PassKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PassKit.framework; path = System/Library/Frameworks/PassKit.framework; sourceTree = SDKROOT; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; + DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -384,6 +386,7 @@ children = ( DDC4D567275499A500A4208E /* Persistence.swift */, DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */, + DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */, ); path = Persistence; sourceTree = ""; @@ -576,6 +579,7 @@ DD47E3D626F17ED900029299 /* CircleText.swift in Sources */, DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */, DD17E5DE277D49D400010EC2 /* storeforward.pb.swift in Sources */, + DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */, C9A88B55278B503C00BD810A /* MapViewModule.swift in Sources */, DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */, C9483F6D2773017500998F6B /* MapView.swift in Sources */, diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index ae9eb44d..5f0165d5 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -427,6 +427,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())") } case .positionApp: positionPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) + case .waypointApp: + if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Waypoint App UNHANDLED \(try! decodedInfo.packet.jsonString())") } case .nodeinfoApp: nodeInfoAppPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!) case .routingApp: diff --git a/MeshtasticClient/Helpers/MeshPackets.swift b/MeshtasticClient/Helpers/MeshPackets.swift index 3ed4edad..e84abb0c 100644 --- a/MeshtasticClient/Helpers/MeshPackets.swift +++ b/MeshtasticClient/Helpers/MeshPackets.swift @@ -430,6 +430,8 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj fetchedMessage!.ackSNR = packet.rxSnr fetchedMessage!.ackTimestamp = Int32(packet.rxTime) fetchedMessage!.objectWillChange.send() + fetchedMessage!.fromUser?.objectWillChange.send() + fetchedMessage!.toUser?.objectWillChange.send() } try context.save() diff --git a/MeshtasticClient/Persistence/UserEntityExtension.swift b/MeshtasticClient/Persistence/UserEntityExtension.swift new file mode 100644 index 00000000..23283eef --- /dev/null +++ b/MeshtasticClient/Persistence/UserEntityExtension.swift @@ -0,0 +1,16 @@ +// +// UserEntityExtension.swift +// MeshtasticClient +// +// Created by Garth Vander Houwen on 6/3/22. +// + +import Foundation + +extension UserEntity { + + var messageList: [MessageEntity] { + + self.value(forKey: "allMessages") as! [MessageEntity] + } +} diff --git a/MeshtasticClient/Protobufs/config.pb.swift b/MeshtasticClient/Protobufs/config.pb.swift index e411e8ea..4a1bff42 100644 --- a/MeshtasticClient/Protobufs/config.pb.swift +++ b/MeshtasticClient/Protobufs/config.pb.swift @@ -283,20 +283,6 @@ struct Config { /// window. var gpsAttemptTime: UInt32 = 0 - /// - /// Shall we accept 2D GPS fixes? By default, only 3D fixes are accepted - /// (during a 2D fix, altitude values are unreliable and will be excluded) - var gpsAccept2D: Bool = false - - /// - /// GPS maximum DOP accepted (dilution of precision) - /// Set a rejection threshold for GPS readings based on their precision, - /// relative to the GPS rated accuracy (which is typically ~3m) - /// Solutions above this value will be treated as retryable errors! - /// Useful range is between 1 - 64 (3m - <~200m) - /// By default (if zero), accept all GPS readings - var gpsMaxDop: UInt32 = 0 - /// /// Bit field of boolean configuration options for POSITION messages /// (bitwise OR of PositionFlags) @@ -1294,8 +1280,6 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm 5: .standard(proto: "gps_disabled"), 6: .standard(proto: "gps_update_interval"), 7: .standard(proto: "gps_attempt_time"), - 8: .standard(proto: "gps_accept_2d"), - 9: .standard(proto: "gps_max_dop"), 10: .standard(proto: "position_flags"), ] @@ -1311,8 +1295,6 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm case 5: try { try decoder.decodeSingularBoolField(value: &self.gpsDisabled) }() case 6: try { try decoder.decodeSingularUInt32Field(value: &self.gpsUpdateInterval) }() case 7: try { try decoder.decodeSingularUInt32Field(value: &self.gpsAttemptTime) }() - case 8: try { try decoder.decodeSingularBoolField(value: &self.gpsAccept2D) }() - case 9: try { try decoder.decodeSingularUInt32Field(value: &self.gpsMaxDop) }() case 10: try { try decoder.decodeSingularUInt32Field(value: &self.positionFlags) }() default: break } @@ -1338,12 +1320,6 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm if self.gpsAttemptTime != 0 { try visitor.visitSingularUInt32Field(value: self.gpsAttemptTime, fieldNumber: 7) } - if self.gpsAccept2D != false { - try visitor.visitSingularBoolField(value: self.gpsAccept2D, fieldNumber: 8) - } - if self.gpsMaxDop != 0 { - try visitor.visitSingularUInt32Field(value: self.gpsMaxDop, fieldNumber: 9) - } if self.positionFlags != 0 { try visitor.visitSingularUInt32Field(value: self.positionFlags, fieldNumber: 10) } @@ -1357,8 +1333,6 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm if lhs.gpsDisabled != rhs.gpsDisabled {return false} if lhs.gpsUpdateInterval != rhs.gpsUpdateInterval {return false} if lhs.gpsAttemptTime != rhs.gpsAttemptTime {return false} - if lhs.gpsAccept2D != rhs.gpsAccept2D {return false} - if lhs.gpsMaxDop != rhs.gpsMaxDop {return false} if lhs.positionFlags != rhs.positionFlags {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true diff --git a/MeshtasticClient/Views/Messages/UserMessageList.swift b/MeshtasticClient/Views/Messages/UserMessageList.swift index 80ae8b81..e6abebef 100644 --- a/MeshtasticClient/Views/Messages/UserMessageList.swift +++ b/MeshtasticClient/Views/Messages/UserMessageList.swift @@ -40,20 +40,18 @@ struct UserMessageList: View { let hasTapbackSupport = minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedSame VStack { - - let allMessages = user.value(forKey: "allMessages") as! [MessageEntity] ScrollViewReader { scrollView in ScrollView { - if allMessages.count > 0 { + if user.messageList.count > 0 { HStack{ // Padding at the top of the message list }.padding(.bottom) - ForEach( allMessages ) { (message: MessageEntity) in + ForEach( user.messageList ) { (message: MessageEntity) in let currentUser: Bool = (bleManager.connectedPeripheral == nil) ? false : ((bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : false ) From afbf626dd1006ae11e975405851cb0b0506ad97f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 4 Jun 2022 09:53:59 -0700 Subject: [PATCH 10/12] Fix for scroll view flashing bug when new messages are received --- Meshtastic Client.xcodeproj/project.pbxproj | 5 ++- .../Views/Bluetooth/Connect.swift | 2 +- .../Views/Messages/UserMessageList.swift | 35 ++++++++----------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 2a7c36dd..d292f5d1 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -761,7 +761,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -793,7 +793,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 10; + CURRENT_PROJECT_VERSION = 15; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -809,7 +809,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; - SWIFT_OBJC_BRIDGING_HEADER = "MeshtasticClient/Compression/MeshtasticClient-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/MeshtasticClient/Views/Bluetooth/Connect.swift b/MeshtasticClient/Views/Bluetooth/Connect.swift index d6d9b0c7..2f0f5073 100644 --- a/MeshtasticClient/Views/Bluetooth/Connect.swift +++ b/MeshtasticClient/Views/Bluetooth/Connect.swift @@ -24,7 +24,7 @@ struct Connect: View { var body: some View { let firmwareVersion = bleManager.lastConnnectionVersion - let minimumVersion = "1.3.12" + let minimumVersion = "1.3.15" let supportedVersion = firmwareVersion == "0.0.0" || minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedSame NavigationView { diff --git a/MeshtasticClient/Views/Messages/UserMessageList.swift b/MeshtasticClient/Views/Messages/UserMessageList.swift index e6abebef..f9acc49b 100644 --- a/MeshtasticClient/Views/Messages/UserMessageList.swift +++ b/MeshtasticClient/Views/Messages/UserMessageList.swift @@ -31,7 +31,7 @@ struct UserMessageList: View { @State private var replyMessageId: Int64 = 0 @State private var sendPositionWithMessage: Bool = false - @State var messageCount = 0 + @State private var messageCount = 0 var body: some View { @@ -46,11 +46,7 @@ struct UserMessageList: View { ScrollView { if user.messageList.count > 0 { - - HStack{ - // Padding at the top of the message list - }.padding(.bottom) - + ForEach( user.messageList ) { (message: MessageEntity) in let currentUser: Bool = (bleManager.connectedPeripheral == nil) ? false : ((bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : false ) @@ -59,7 +55,7 @@ struct UserMessageList: View { if message.replyID > 0 { - let messageReply = allMessages.first(where: { $0.messageId == message.replyID }) + let messageReply = user.messageList.first(where: { $0.messageId == message.replyID }) HStack { @@ -297,12 +293,13 @@ struct UserMessageList: View { } .padding(.bottom) - .id(allMessages.firstIndex(of: message)) + .id(user.messageList.firstIndex(of: message)) if !currentUser { Spacer(minLength:50) } } + .id(message.messageId) .padding([.leading, .trailing]) .frame(maxWidth: .infinity) .alert(isPresented: $showDeleteMessageAlert) { @@ -311,7 +308,7 @@ struct UserMessageList: View { print("OK button tapped") if deleteMessageId > 0 { - let message = allMessages.first(where: { $0.messageId == deleteMessageId }) + let message = user.messageList.first(where: { $0.messageId == deleteMessageId }) context.delete(message!) do { @@ -336,19 +333,17 @@ struct UserMessageList: View { self.bleManager.context = context self.bleManager.userSettings = userSettings - - if allMessages.count > 1 { - - scrollView.scrollTo(allMessages.firstIndex(of: allMessages.last! ), anchor: .bottom) - } + + messageCount = user.messageList.count + }) - .onChange(of: allMessages.count, perform: { count in + .onChange(of: messageCount, perform: { value in + //scrollView.scrollTo(user.messageList.firstIndex(of: user.messageList.last! ), anchor: .bottom) + scrollView.scrollTo(user.messageList.last!.messageId) + }) + .onChange(of: user.messageList, perform: { messages in - if count > 1 { - - scrollView.scrollTo(allMessages.firstIndex(of: allMessages.last! ), anchor: .bottom) - - } + messageCount = messages.count }) } From 7b920aba1e65d3e131fc532770a893fd2e01e878 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 6 Jun 2022 22:24:35 -0700 Subject: [PATCH 11/12] 1.3 Updates --- Meshtastic Client.xcodeproj/project.pbxproj | 8 ++++-- MeshtasticClient/Helpers/BLEManager.swift | 11 +++++--- MeshtasticClient/Helpers/ChannelHelper.swift | 26 +++++++++++++++++++ MeshtasticClient/Helpers/MeshPackets.swift | 2 ++ .../Views/Messages/UserMessageList.swift | 3 +++ 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 MeshtasticClient/Helpers/ChannelHelper.swift diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index d292f5d1..8a302fa9 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; }; + DDE393BA284E535700473991 /* ChannelHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE393B9284E535700473991 /* ChannelHelper.swift */; }; DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; }; /* End PBXBuildFile section */ @@ -142,6 +143,7 @@ DDCA31312826009C00207175 /* PassKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PassKit.framework; path = System/Library/Frameworks/PassKit.framework; sourceTree = SDKROOT; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = ""; }; + DDE393B9284E535700473991 /* ChannelHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelHelper.swift; sourceTree = ""; }; DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -377,6 +379,7 @@ DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */, DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */, DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */, + DDE393B9284E535700473991 /* ChannelHelper.swift */, ); path = Helpers; sourceTree = ""; @@ -589,6 +592,7 @@ DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */, DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */, DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */, + DDE393BA284E535700473991 /* ChannelHelper.swift in Sources */, C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */, DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */, DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, @@ -761,7 +765,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; @@ -793,7 +797,7 @@ CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 11; DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\""; DEVELOPMENT_TEAM = GCH7VS5Y9R; ENABLE_PREVIEWS = YES; diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 5f0165d5..ce78febd 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -194,9 +194,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } - let today = Date() - let visibleDuration = Calendar.current.date(byAdding: .second, value: -5, to: today)! - peripherals.removeAll(where: { $0.lastUpdate <= visibleDuration}) + // let today = Date() + // let visibleDuration = Calendar.current.date(byAdding: .second, value: -7, to: Date())! + // peripherals.removeAll(where: { $0.lastUpdate <= visibleDuration}) } // Called when a peripheral is connected @@ -296,6 +296,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } } + + func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) { + + print(invalidatedServices) + } // MARK: Discover Characteristics Event func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { diff --git a/MeshtasticClient/Helpers/ChannelHelper.swift b/MeshtasticClient/Helpers/ChannelHelper.swift new file mode 100644 index 00000000..0c7f42ed --- /dev/null +++ b/MeshtasticClient/Helpers/ChannelHelper.swift @@ -0,0 +1,26 @@ +// +// ChannelHelper.swift +// MeshtasticClient +// +// Created by Garth Vander Houwen on 6/6/22. +// + +//import Foundation +// +// +//private fun urlToChannels(url: Uri): AppOnlyProtos.ChannelSet { +// val urlStr = url.toString() +// +// val pathRegex = Regex("$prefix(.*)", RegexOption.IGNORE_CASE) +// val (base64) = pathRegex.find(urlStr)?.destructured +// ?: throw MalformedURLException("Not a meshtastic URL: ${urlStr.take(40)}") +// val bytes = Base64.decode(base64, base64Flags) +// +// return AppOnlyProtos.ChannelSet.parseFrom(bytes) +// } +// +//func urlToChannels(url: URL) { +// +// let urlString = url.absoluteString +// +//} diff --git a/MeshtasticClient/Helpers/MeshPackets.swift b/MeshtasticClient/Helpers/MeshPackets.swift index e84abb0c..483629b7 100644 --- a/MeshtasticClient/Helpers/MeshPackets.swift +++ b/MeshtasticClient/Helpers/MeshPackets.swift @@ -550,6 +550,8 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, meshLogging: newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from }) newMessage.messagePayload = messageText + newMessage.fromUser?.objectWillChange.send() + newMessage.toUser?.objectWillChange.send() do { diff --git a/MeshtasticClient/Views/Messages/UserMessageList.swift b/MeshtasticClient/Views/Messages/UserMessageList.swift index f9acc49b..98340563 100644 --- a/MeshtasticClient/Views/Messages/UserMessageList.swift +++ b/MeshtasticClient/Views/Messages/UserMessageList.swift @@ -32,6 +32,7 @@ struct UserMessageList: View { @State private var sendPositionWithMessage: Bool = false @State private var messageCount = 0 + @State private var refreshId = UUID() var body: some View { @@ -335,6 +336,7 @@ struct UserMessageList: View { self.bleManager.userSettings = userSettings messageCount = user.messageList.count + refreshId = UUID() }) .onChange(of: messageCount, perform: { value in @@ -343,6 +345,7 @@ struct UserMessageList: View { }) .onChange(of: user.messageList, perform: { messages in + refreshId = UUID() messageCount = messages.count }) } From bd7fdbb02a1c3e2f66204c437291686129aa8114 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 6 Jun 2022 22:36:56 -0700 Subject: [PATCH 12/12] Update Package.resolved Revert to v1 --- .../xcshareddata/swiftpm/Package.resolved | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Meshtastic Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meshtastic Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bccfe8c9..6685f05b 100644 --- a/Meshtastic Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Meshtastic Client.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,23 +1,25 @@ { - "pins" : [ - { - "identity" : "sqlite.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/stephencelis/SQLite.swift.git", - "state" : { - "revision" : "60a65015f6402b7c34b9a924f755ca0a73afeeaa", - "version" : "0.13.1" + "object": { + "pins": [ + { + "package": "SQLite.swift", + "repositoryURL": "https://github.com/stephencelis/SQLite.swift.git", + "state": { + "branch": null, + "revision": "60a65015f6402b7c34b9a924f755ca0a73afeeaa", + "version": "0.13.1" + } + }, + { + "package": "SwiftProtobuf", + "repositoryURL": "https://github.com/apple/swift-protobuf.git", + "state": { + "branch": null, + "revision": "e1499bc69b9040b29184f7f2996f7bab467c1639", + "version": "1.19.0" + } } - }, - { - "identity" : "swift-protobuf", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-protobuf.git", - "state" : { - "revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639", - "version" : "1.19.0" - } - } - ], - "version" : 2 + ] + }, + "version": 1 }