From 01b22180b7da69d9d84e74c5324e07a446a2812b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Sep 2022 05:44:04 -0700 Subject: [PATCH 01/36] Update node detail to use NavigationSplitView --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Helpers/BLEManager.swift | 6 +-- .../Views/Messages/UserMessageList.swift | 1 + Meshtastic/Views/Nodes/NodeList.swift | 53 +++++++++++-------- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index a59a61fd..fce90b9c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -953,7 +953,7 @@ ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -985,7 +985,7 @@ ENABLE_PREVIEWS = YES; INFOPLIST_FILE = Meshtastic/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Meshtastic; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index a7337c94..75e4aa07 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -114,7 +114,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if centralManager.isScanning { self.centralManager.stopScan() - self.isScanning = self.centralManager.isScanning + // self.isScanning = self.centralManager.isScanning print("🛑 Stopped Scanning") } } @@ -170,7 +170,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.disconnectPeripheral() } - self.connectedVersion = "0.0.0" + //self.connectedVersion = "0.0.0" self.centralManager?.connect(peripheral) // Invalidate any existing timer @@ -223,7 +223,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } let today = Date() - let visibleDuration = Calendar.current.date(byAdding: .second, value: -2, to: today)! + let visibleDuration = Calendar.current.date(byAdding: .second, value: -4, to: today)! peripherals.removeAll(where: { $0.lastUpdate <= visibleDuration}) } diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index 45646651..f8c164fe 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -359,6 +359,7 @@ struct UserMessageList: View { .listRowSeparator(.hidden) } } + .scrollDismissesKeyboard(.interactively) .onAppear(perform: { self.bleManager.context = context diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 80239cf4..616a35c7 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -20,35 +20,35 @@ struct NodeList: View { @State var initialLoad: Bool = true @FetchRequest( - sortDescriptors: [NSSortDescriptor(key: "user.shortName", ascending: true)], + sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)], animation: .default) private var nodes: FetchedResults - @State private var selection: String? = "" + @State private var selection: NodeInfoEntity? = nil // Nothing selected by default. var body: some View { - - NavigationView { - - List { + + NavigationSplitView { + + List (nodes, id: \.self, selection: $selection) { node in if nodes.count == 0 { - Text("Scan for Radios").font(.largeTitle) - Text("No Meshtastic Nodes Found").font(.title2) - Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your iPhone, iPad or Mac.") - .font(.body) - Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") - Text("Views with bluetooth functionality will show an indicator in the upper right hand corner show if bluetooth is on, and if a device is connected.") + Text("Scan for Radios").font(.largeTitle) + Text("No Meshtastic Nodes Found").font(.title2) + Text("Go to the bluetooth section in the bottom right menu and click the Start Scanning button to scan for nearby radios and find your Meshtastic device. Make sure your device is powered on and near your iPhone, iPad or Mac.") + .font(.body) + Text("Once the device shows under Available Devices touch the device you want to connect to and it will pull node information over BLE and populate the node list and mesh map in the Meshtastic app.") + Text("Views with bluetooth functionality will show an indicator in the upper right hand corner showing if bluetooth is on, and if a device is connected.") .listRowSeparator(.visible) - } else { - ForEach( nodes ) { node in + } else { + //ForEach( nodes ) { node in let index = nodes.firstIndex(where: { $0.id == node.id }) - NavigationLink(destination: NodeDetail(node: node), tag: String(index!), selection: $selection) { + NavigationLink(value: node) { let connected: Bool = (bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == node.num) @@ -123,11 +123,10 @@ struct NodeList: View { } .padding([.leading, .top, .bottom]) } - .isDetailLink(false) - } - } - } - .navigationTitle("All Nodes") + //} + } + } + .navigationTitle("All Nodes") .onAppear { if initialLoad { @@ -137,8 +136,16 @@ struct NodeList: View { self.initialLoad = false } } - } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .navigationViewStyle(DoubleColumnNavigationViewStyle()) + } detail: { + + if let node = selection { + + NodeDetail(node:node) + + } else { + + Text("Select a node") + } + } } } From f5463e3633975cab548ea08297919cc1c51f24f0 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Sep 2022 07:10:02 -0700 Subject: [PATCH 02/36] Updates for node details --- Meshtastic/MeshtasticApp.swift | 2 +- Meshtastic/Views/Nodes/NodeDetail.swift | 31 +++++++++++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Meshtastic/MeshtasticApp.swift b/Meshtastic/MeshtasticApp.swift index 679ebb1d..227f70c4 100644 --- a/Meshtastic/MeshtasticApp.swift +++ b/Meshtastic/MeshtasticApp.swift @@ -31,7 +31,7 @@ struct MeshtasticAppleApp: App { saveQR = true } - print("User wants to open URL: \(channelUrl?.relativeString)") + print("User wants to open URL: \(String(describing: channelUrl?.relativeString))") } .sheet(isPresented: $saveQR) { diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 80b9ccbb..9754c36b 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -23,7 +23,7 @@ struct NodeDetail: View { let hwModelString = node.user?.hwModel ?? "UNSET" - ZStack { + NavigationStack { GeometryReader { bounds in @@ -64,7 +64,7 @@ struct NodeDetail: View { ) } .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.6) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2) } } Text("Sats: \(mostRecent.satsInView)").offset( y:-40) @@ -399,45 +399,47 @@ struct NodeDetail: View { Text("MAC Address: ") Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) } + .padding([.bottom], 0) } - List { + VStack { if (node.positions?.count ?? 0) > 0 { + Divider() NavigationLink { LocationHistory(node: node) } label: { - + Image(systemName: "building.columns") .symbolRenderingMode(.hierarchical) .font(.title) - - Text("Position History \(node.positions?.count ?? 0) Points") + + Text("Position History (\(node.positions?.count ?? 0) Points)") .font(.title3) } .fixedSize(horizontal: false, vertical: true) } + if (node.telemetries?.count ?? 0) > 0 { + + Divider() NavigationLink { TelemetryLog(node: node) } label: { - + Image(systemName: "chart.xyaxis.line") .symbolRenderingMode(.hierarchical) .font(.title) - - Text("Telemetry Log \(node.telemetries?.count ?? 0) Readings") + + Text("Telemetry Log (\(node.telemetries?.count ?? 0) Readings)") .font(.title3) } - .fixedSize(horizontal: false, vertical: true) + Divider() } } - .listStyle(GroupedListStyle()) - .frame(minHeight: 170) - .padding(0) } - .offset( y: (node.myInfo?.hasGps ?? false ? 0 : -40)) + .offset( y:-40) } .edgesIgnoringSafeArea([.leading, .trailing]) } @@ -462,7 +464,6 @@ struct NodeDetail: View { self.initialLoad = false } } - } } From 9a17b65abe2df29e37e59b077401daff5fe02cf8 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Sep 2022 09:56:03 -0700 Subject: [PATCH 03/36] Clean up node list and details views --- Meshtastic/Helpers/BLEManager.swift | 6 +-- Meshtastic/Views/Nodes/LocationHistory.swift | 4 +- Meshtastic/Views/Nodes/NodeDetail.swift | 40 ++++++++++---------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 75e4aa07..b8009945 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -114,7 +114,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if centralManager.isScanning { self.centralManager.stopScan() - // self.isScanning = self.centralManager.isScanning + isScanning = self.centralManager.isScanning print("🛑 Stopped Scanning") } } @@ -170,7 +170,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.disconnectPeripheral() } - //self.connectedVersion = "0.0.0" + self.connectedVersion = "0.0.0" self.centralManager?.connect(peripheral) // Invalidate any existing timer @@ -223,7 +223,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } let today = Date() - let visibleDuration = Calendar.current.date(byAdding: .second, value: -4, to: today)! + let visibleDuration = Calendar.current.date(byAdding: .second, value: -2, to: today)! peripherals.removeAll(where: { $0.lastUpdate <= visibleDuration}) } diff --git a/Meshtastic/Views/Nodes/LocationHistory.swift b/Meshtastic/Views/Nodes/LocationHistory.swift index 52165e2d..ad4299e2 100644 --- a/Meshtastic/Views/Nodes/LocationHistory.swift +++ b/Meshtastic/Views/Nodes/LocationHistory.swift @@ -18,8 +18,8 @@ struct LocationHistory: View { var body: some View { - VStack { - + NavigationStack { + List { ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 9754c36b..7a91e6fe 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -63,8 +63,8 @@ struct NodeDetail: View { } ) } - .ignoresSafeArea(.all, edges: [.leading, .trailing]) - .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2) + .ignoresSafeArea(.all, edges: [.leading, .trailing]) + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 1.70) } } Text("Sats: \(mostRecent.satsInView)").offset( y:-40) @@ -440,28 +440,30 @@ struct NodeDetail: View { } } .offset( y:-40) + .padding(.bottom, -40) } .edgesIgnoringSafeArea([.leading, .trailing]) - } - } - .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems(trailing: + .navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown") + .navigationBarTitleDisplayMode(.inline) + .padding(.bottom, 10) + .navigationBarItems(trailing: - ZStack { + ZStack { - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - } - ) - .onAppear { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + } + ) + .onAppear { - if self.initialLoad{ - - self.bleManager.context = context - self.initialLoad = false + if self.initialLoad{ + + self.bleManager.context = context + self.initialLoad = false + } + } } } } From f191d9112ba79a70b56e2a41298dd11fa5f725ab Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 13 Sep 2022 21:06:54 -0700 Subject: [PATCH 04/36] Add grid to location history --- Meshtastic/Views/Nodes/LocationHistory.swift | 105 ++++++------------- Meshtastic/Views/Nodes/NodeList.swift | 2 +- 2 files changed, 35 insertions(+), 72 deletions(-) diff --git a/Meshtastic/Views/Nodes/LocationHistory.swift b/Meshtastic/Views/Nodes/LocationHistory.swift index ad4299e2..f638d27c 100644 --- a/Meshtastic/Views/Nodes/LocationHistory.swift +++ b/Meshtastic/Views/Nodes/LocationHistory.swift @@ -20,81 +20,45 @@ struct LocationHistory: View { NavigationStack { - List { + ScrollView { - ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + //Add a table for mac and ipad + } + + GridRow { - VStack { - - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - HStack { - - Image(systemName: "mappin.and.ellipse") - .foregroundColor(.accentColor) - .font(.callout) - - Text("Lat/Long:").font(.callout) - Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "arrow.up.arrow.down.circle") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Alt:") - .font(.callout) - - Text("\(String(mappin.altitude))m") - .foregroundColor(.gray) - .font(.callout) - Image(systemName: "clock.badge.checkmark.fill") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .font(.callout) - DateTimeText(dateTime: mappin.time) - .foregroundColor(.gray) - .font(.callout) - - } - - } else { - - HStack { - - Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline) - Text("Lat/Long:").font(.caption) - Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") - .foregroundColor(.gray) - .font(.caption) - - } - - HStack { - - Image(systemName: "arrow.up.arrow.down.circle") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Alt:") - .font(.caption) - Text("\(String(mappin.altitude))m") - .foregroundColor(.gray) - .font(.caption) - Image(systemName: "clock.badge.checkmark.fill") - .font(.subheadline) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - DateTimeText(dateTime: mappin.time) - .foregroundColor(.gray) - .font(.caption) - } + Text("Latitude") + .font(.caption) + .fontWeight(.bold) + Text("Longitude") + .font(.caption) + .fontWeight(.bold) + Text("Alt.") + .font(.caption) + .fontWeight(.bold) + Text("Date / Time") + .font(.caption) + .fontWeight(.bold) + } + Divider() + ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in + GridRow { + Text(String(mappin.latitude ?? 0)) + .font(.caption) + Text(String(mappin.longitude ?? 0)) + .font(.caption) + Text(String(mappin.altitude)) + .font(.caption) + Text(mappin.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.caption) } } } + .padding(.leading, 15) + .padding(.trailing, 5) } Button { @@ -129,7 +93,6 @@ struct LocationHistory: View { } ) .navigationTitle("Location History \(node.positions?.count ?? 0) Points") - .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: ZStack { diff --git a/Meshtastic/Views/Nodes/NodeList.swift b/Meshtastic/Views/Nodes/NodeList.swift index 616a35c7..2ab5327a 100644 --- a/Meshtastic/Views/Nodes/NodeList.swift +++ b/Meshtastic/Views/Nodes/NodeList.swift @@ -46,7 +46,7 @@ struct NodeList: View { } else { //ForEach( nodes ) { node in - let index = nodes.firstIndex(where: { $0.id == node.id }) + // let index = nodes.firstIndex(where: { $0.id == node.id }) NavigationLink(value: node) { From d1f644fa4793268f8c2196f626315e9207476b18 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 14 Sep 2022 06:50:27 -0700 Subject: [PATCH 05/36] Device metrics screen, adapted to use grid --- Meshtastic.xcodeproj/project.pbxproj | 20 +-- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 114 ++++++++++++++++++ ...yLog.swift => EnvironmentMetricsLog.swift} | 13 +- Meshtastic/Views/Nodes/NodeDetail.swift | 20 ++- ...ocationHistory.swift => PositionLog.swift} | 6 +- 5 files changed, 157 insertions(+), 16 deletions(-) create mode 100644 Meshtastic/Views/Nodes/DeviceMetricsLog.swift rename Meshtastic/Views/Nodes/{TelemetryLog.swift => EnvironmentMetricsLog.swift} (98%) rename Meshtastic/Views/Nodes/{LocationHistory.swift => PositionLog.swift} (94%) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index fce90b9c..82040e32 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -36,7 +36,7 @@ DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158B2824A91E0032668E /* module_config.pb.swift */; }; DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4C158D2824AA7E0032668E /* config.pb.swift */; }; DD4DED9027AD2975004BA27E /* cannedmessages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4DED8F27AD2975004BA27E /* cannedmessages.pb.swift */; }; - DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */; }; + DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */; }; DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DD5394FB276993AD00AD86B1 /* SwiftProtobuf */; }; DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */; }; DD539502276DAA6A00AD86B1 /* MapLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD539501276DAA6A00AD86B1 /* MapLocation.swift */; }; @@ -44,7 +44,8 @@ DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannel.swift */; }; - DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* LocationHistory.swift */; }; + DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; }; + DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; }; DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; }; DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; }; DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FE272476C700F4AB02 /* LogDocument.swift */; }; @@ -148,7 +149,7 @@ DD4C158B2824A91E0032668E /* module_config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = module_config.pb.swift; sourceTree = ""; }; DD4C158D2824AA7E0032668E /* config.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = config.pb.swift; sourceTree = ""; }; DD4DED8F27AD2975004BA27E /* cannedmessages.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = cannedmessages.pb.swift; sourceTree = ""; }; - DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryLog.swift; sourceTree = ""; }; + DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentMetricsLog.swift; sourceTree = ""; }; DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = ""; }; DD5929A528C0F292003DB21D /* MeshtasticDataModel v 9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 9.xcdatamodel"; sourceTree = ""; }; @@ -157,7 +158,8 @@ DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; DD6B85A728009258000ACD6B /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = ""; }; - DD73FD1028750779000852D6 /* LocationHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHistory.swift; sourceTree = ""; }; + DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; + DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = ""; }; @@ -281,8 +283,9 @@ DD47E3CD26F103C600029299 /* NodeList.swift */, DD90860D26F69BAE00DC5189 /* NodeMap.swift */, DD2E65252767A01F00E45FC5 /* NodeDetail.swift */, - DD73FD1028750779000852D6 /* LocationHistory.swift */, - DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */, + DD73FD1028750779000852D6 /* PositionLog.swift */, + DD4F23CC28779A3C001D37CB /* EnvironmentMetricsLog.swift */, + DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */, ); path = Nodes; sourceTree = ""; @@ -712,9 +715,10 @@ DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, DDB6ABE828B141AF00384BA1 /* WiFiModes.swift in Sources */, - DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */, + DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */, DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */, DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, + DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */, @@ -774,7 +778,7 @@ DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */, DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, - DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */, + DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */, diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift new file mode 100644 index 00000000..fbb5065d --- /dev/null +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -0,0 +1,114 @@ +// +// DeviceMetricsLog.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/7/22. +// +import SwiftUI + +struct DeviceMetricsLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + @State var isExporting = false + @State var exportString = "" + + var node: NodeInfoEntity + + var body: some View { + + NavigationStack { + + ScrollView { + + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + GridRow { + + Text("Bat%") + .font(.callout) + .fontWeight(.bold) + Text("Voltage") + .font(.callout) + .fontWeight(.bold) + Text("ChUtil") + .font(.callout) + .fontWeight(.bold) + Text("AirTm") + .font(.callout) + .fontWeight(.bold) + Text("Timestamp") + .font(.callout) + .fontWeight(.bold) + } + Divider() + ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (dm: TelemetryEntity) in + + if dm.metricsType == 0 { + + GridRow { + Text(String(dm.batteryLevel)) + .font(.callout) + Text(String(dm.voltage)) + .font(.callout) + Text(String(dm.channelUtilization)) + .font(.callout) + Text(String(dm.airUtilTx)) + .font(.callout) + Text(dm.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.callout) + } + } + } + } + .padding(.leading, 15) + .padding(.trailing, 5) + } + } + Button { + + exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) + isExporting = true + + } label: { + + Label("Save", systemImage: "square.and.arrow.down") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .navigationTitle("Device Metrics Log") + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + self.bleManager.context = context + } + .fileExporter( + isPresented: $isExporting, + document: CsvDocument(emptyCsv: exportString), + contentType: .commaSeparatedText, + defaultFilename: String("\(node.user!.longName ?? "Node") Device Telemetry Log"), + onCompletion: { result in + + if case .success = result { + + print("Device Telemetry log download succeeded.") + + self.isExporting = false + + } else { + + print("Device Telemetry log download failed: \(result).") + } + } + ) + } +} diff --git a/Meshtastic/Views/Nodes/TelemetryLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift similarity index 98% rename from Meshtastic/Views/Nodes/TelemetryLog.swift rename to Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index b43cfc09..1c30437e 100644 --- a/Meshtastic/Views/Nodes/TelemetryLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -6,7 +6,7 @@ // import SwiftUI -struct TelemetryLog: View { +struct EnvironmentMetricsLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -18,6 +18,17 @@ struct TelemetryLog: View { var body: some View { + NavigationStack { + + ScrollView { + + Grid(alignment: .topLeading, horizontalSpacing: 2) { + + } + } + + } + List { ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (tel: TelemetryEntity) in diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 7a91e6fe..41d5164f 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -408,14 +408,14 @@ struct NodeDetail: View { Divider() NavigationLink { - LocationHistory(node: node) + PositionLog(node: node) } label: { Image(systemName: "building.columns") .symbolRenderingMode(.hierarchical) .font(.title) - Text("Position History (\(node.positions?.count ?? 0) Points)") + Text("Position Log (\(node.positions?.count ?? 0) Points)") .font(.title3) } .fixedSize(horizontal: false, vertical: true) @@ -425,14 +425,26 @@ struct NodeDetail: View { Divider() NavigationLink { - TelemetryLog(node: node) + DeviceMetricsLog(node: node) + } label: { + + Image(systemName: "flipphone") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Device Metrics Log") + .font(.title3) + } + Divider() + NavigationLink { + EnvironmentMetricsLog(node: node) } label: { Image(systemName: "chart.xyaxis.line") .symbolRenderingMode(.hierarchical) .font(.title) - Text("Telemetry Log (\(node.telemetries?.count ?? 0) Readings)") + Text("Environment Metrics Log") .font(.title3) } Divider() diff --git a/Meshtastic/Views/Nodes/LocationHistory.swift b/Meshtastic/Views/Nodes/PositionLog.swift similarity index 94% rename from Meshtastic/Views/Nodes/LocationHistory.swift rename to Meshtastic/Views/Nodes/PositionLog.swift index f638d27c..e5e6d54c 100644 --- a/Meshtastic/Views/Nodes/LocationHistory.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -6,7 +6,7 @@ // import SwiftUI -struct LocationHistory: View { +struct PositionLog: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -39,7 +39,7 @@ struct LocationHistory: View { Text("Alt.") .font(.caption) .fontWeight(.bold) - Text("Date / Time") + Text("Timestamp") .font(.caption) .fontWeight(.bold) } @@ -92,7 +92,7 @@ struct LocationHistory: View { } } ) - .navigationTitle("Location History \(node.positions?.count ?? 0) Points") + .navigationTitle("Position Log \(node.positions?.count ?? 0) Points") .navigationBarItems(trailing: ZStack { From d88abd4b4c8ec7e9e6fed8e22c6e9c5d4f807636 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 14 Sep 2022 07:29:31 -0700 Subject: [PATCH 06/36] Environment metrics log --- Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 20 +- .../Views/Nodes/EnvironmentMetricsLog.swift | 383 +++--------------- 2 files changed, 68 insertions(+), 335 deletions(-) diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index fbb5065d..6f576fe9 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -26,7 +26,7 @@ struct DeviceMetricsLog: View { GridRow { - Text("Bat%") + Text("Batt") .font(.callout) .fontWeight(.bold) Text("Voltage") @@ -48,13 +48,23 @@ struct DeviceMetricsLog: View { if dm.metricsType == 0 { GridRow { - Text(String(dm.batteryLevel)) - .font(.callout) + + if dm.batteryLevel == 0 { + + Text("USB") + .font(.callout) + + } else { + + Text("\(String(dm.batteryLevel))%") + .font(.callout) + } + Text(String(dm.voltage)) .font(.callout) - Text(String(dm.channelUtilization)) + Text("\(String(format: "%.2f", dm.channelUtilization))%") .font(.callout) - Text(String(dm.airUtilTx)) + Text("\(String(format: "%.2f", dm.airUtilTx))%") .font(.callout) Text(dm.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") .font(.callout) diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 1c30437e..655e693e 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -1,5 +1,5 @@ // -// TelemetryLog.swift +// DeviceMetricsLog.swift // Meshtastic // // Copyright(c) Garth Vander Houwen 7/7/22. @@ -24,344 +24,67 @@ struct EnvironmentMetricsLog: View { Grid(alignment: .topLeading, horizontalSpacing: 2) { - } - } - - } - - List { - - ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (tel: TelemetryEntity) in - - VStack (alignment: .leading) { - - if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - + GridRow { - if tel.metricsType == 0 { - - // Device Metrics - HStack { - - Text("Device Metrics") - .font(.title) - - BatteryIcon(batteryLevel: tel.batteryLevel, font: .callout, color: .accentColor) - if tel.batteryLevel == 0 { - - Text("Plugged In") - .font(.callout) - .foregroundColor(.gray) - - } else { - - Text("Battery Level: \(String(tel.batteryLevel))%") - .font(.callout) - .foregroundColor(.gray) - } - if tel.batteryLevel > 0 { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - Text("Channel Utilization: \(String(format: "%.2f", tel.channelUtilization))%") - .foregroundColor(.gray) - .font(.callout) - - Text("Airtime Utilization: \(String(format: "%.2f", tel.airUtilTx))%") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } - - } else if tel.metricsType == 1 { - - // Environment Metrics - HStack { - - Text("Environment Metrics") - .font(.title) - - - let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? false)) ? "°C" : "°F" - - if tel.temperature > 0 { - - Image(systemName: "thermometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Temperature: \(String(format: "%.2f", tel.temperature))\(tempReadingType)") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.relativeHumidity > 0 { - - Image(systemName: "humidity") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Relative Humidity: \(String(format: "%.2f", tel.relativeHumidity))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.barometricPressure > 0 { - - Image(systemName: "barometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Barometric Pressure: \(String(format: "%.2f", tel.barometricPressure))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.gasResistance > 0 { - - Image(systemName: "aqi.medium") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Gas Resistance: \(String(format: "%.2f", tel.gasResistance))") - .foregroundColor(.gray) - .font(.callout) - } - - if tel.current > 0 { - - Image(systemName: "directcurrent") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Current: \(String(format: "%.2f", tel.current))") - .foregroundColor(.gray) - .font(.callout) - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(format: "%.2f", tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - - } - } + Text("Temp") + .font(.caption) + .fontWeight(.bold) + Text("Hum") + .font(.caption) + .fontWeight(.bold) + Text("Bar") + .font(.caption) + .fontWeight(.bold) + Text("Gas") + .font(.caption) + .fontWeight(.bold) + Text("Gas") + .font(.caption) + .fontWeight(.bold) + Text("DC") + .font(.caption) + .fontWeight(.bold) + Text("Volt") + .font(.caption) + .fontWeight(.bold) + Text("Timestamp") + .font(.caption) + .fontWeight(.bold) + } + Divider() + ForEach(node.telemetries!.reversed() as! [TelemetryEntity], id: \.self) { (em: TelemetryEntity) in - } else { - - if tel.metricsType == 0 { + if em.metricsType == 1 { - // Device Metrics iPhone Template - VStack { - - HStack { - - Spacer() - Text("Device Metrics") - .font(.title3) - Spacer() - } - - - HStack { - BatteryIcon(batteryLevel: tel.batteryLevel, font: .callout, color: .accentColor) - - if tel.batteryLevel == 0 { - - Text("Plugged In") - .font(.callout) - .foregroundColor(.gray) - - } else { - - Text("Battery Level: \(String(tel.batteryLevel))%") - .font(.callout) - .foregroundColor(.gray) - } - } - HStack { - if tel.batteryLevel > 0 { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - } - - Text("Channel Utilization: \(String(format: "%.2f", tel.channelUtilization))%") - .foregroundColor(.gray) - .font(.callout) - - Text("Airtime Utilization: \(String(format: "%.2f", tel.airUtilTx))%") - .foregroundColor(.gray) - .font(.callout) - HStack { - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } - } - } else if tel.metricsType == 1 { + let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? false)) ? "°C" : "°F" - // Environment Metrics - let tempReadingType = (!(node.telemetryConfig?.environmentDisplayFahrenheit ?? true)) ? "°C" : "°F" - - - // Environment Metrics iPhone Template - VStack { - - HStack { - - Spacer() - Text("Environment Metrics") - .font(.title3) - Spacer() - } + GridRow { - HStack { - - if tel.temperature > 0 { - - Image(systemName: "thermometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Temperature: \(String(format: "%.2f", tel.temperature))\(tempReadingType)") - .foregroundColor(.gray) - .font(.callout) - } - } - - HStack { - - if tel.relativeHumidity > 0 { - - Image(systemName: "humidity") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Relative Humidity: \(String(format: "%.2f", tel.relativeHumidity))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.current > 0 { - - HStack { - - Image(systemName: "directcurrent") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Current: \(String(format: "%.2f", tel.current))") - .foregroundColor(.gray) - .font(.callout) - } - - HStack { - - Image(systemName: "bolt") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Voltage: \(String(format: "%.2f", tel.voltage))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.barometricPressure > 0 { - - HStack { - - Image(systemName: "barometer") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Barometric Pressure: \(String(format: "%.2f", tel.barometricPressure))") - .foregroundColor(.gray) - .font(.callout) - } - } - - if tel.gasResistance > 0 { - - HStack { - - Image(systemName: "aqi.medium") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Gas Resistance: \(String(format: "%.2f", tel.gasResistance))") - .foregroundColor(.gray) - .font(.callout) - } - } - - HStack { - - Image(systemName: "clock.badge.checkmark.fill") - .font(.callout) - .foregroundColor(.accentColor) - .symbolRenderingMode(.hierarchical) - Text("Time:") - .foregroundColor(.gray) - .font(.callout) - DateTimeText(dateTime: tel.time) - .foregroundColor(.gray) - .font(.callout) - } + Text("\(String(format: "%.2f", em.temperature))\(tempReadingType)") + .font(.caption) + Text("\(String(format: "%.2f", em.relativeHumidity))") + .font(.caption) + Text("\(String(format: "%.2f", em.barometricPressure))") + .font(.caption) + Text("\(String(format: "%.2f", em.gasResistance))") + .font(.caption) + Text("\(String(format: "%.2f", em.current))") + .font(.caption) + Text("\(String(format: "%.2f", em.voltage))") + .font(.caption) + Text(em.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") + .font(.caption) } } } } + .padding(.leading, 15) + .padding(.trailing, 5) } } Button { - exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 0) + exportString = TelemetryToCsvFile(telemetry: node.telemetries!.array as! [TelemetryEntity], metricsType: 1) isExporting = true } label: { @@ -372,7 +95,7 @@ struct EnvironmentMetricsLog: View { .buttonBorderShape(.capsule) .controlSize(.large) .padding() - .navigationTitle("Telemetry Log \(node.telemetries?.count ?? 0) Readings") + .navigationTitle("Environment Metrics Log") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: @@ -388,18 +111,18 @@ struct EnvironmentMetricsLog: View { isPresented: $isExporting, document: CsvDocument(emptyCsv: exportString), contentType: .commaSeparatedText, - defaultFilename: String("\(node.user!.longName ?? "Node") Telemetry Log"), + defaultFilename: String("\(node.user!.longName ?? "Node") Environment Metrics Log"), onCompletion: { result in if case .success = result { - print("Telemetry log download succeeded.") + print("Environment metrics log download succeeded.") self.isExporting = false } else { - print("Telemetry log download failed: \(result).") + print("Environment metrics log download failed: \(result).") } } ) From c293244ec671f73c70d12af47d3785854d5a6185 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 14 Sep 2022 19:55:46 -0700 Subject: [PATCH 07/36] Battery level chart, core data model for channels --- Meshtastic.xcodeproj/project.pbxproj | 4 +- Meshtastic/Helpers/BLEManager.swift | 6 + .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 22 +- .../contents | 233 ++++++++++++++++++ Meshtastic/Views/Nodes/DeviceMetricsLog.swift | 29 +++ Meshtastic/Views/Settings/ShareChannel.swift | 11 + 7 files changed, 284 insertions(+), 23 deletions(-) create mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 12.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 82040e32..a1e85937 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ DD6B85A728009258000ACD6B /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = ""; }; DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; + DD769E0428D24E32001A3F05 /* MeshtasticDataModel v 12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 12.xcdatamodel"; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = ""; }; @@ -1167,6 +1168,7 @@ DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + DD769E0428D24E32001A3F05 /* MeshtasticDataModel v 12.xcdatamodel */, DD1925B528CD591B00720036 /* MeshtasticDataModel v 11.xcdatamodel */, DD2160AD28C5536B00C17253 /* MeshtasticDataModel v 10.xcdatamodel */, DD5929A528C0F292003DB21D /* MeshtasticDataModel v 9.xcdatamodel */, @@ -1179,7 +1181,7 @@ DD45C77427BD4EF80011784F /* MeshtasticDataModel v2.xcdatamodel */, DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */, ); - currentVersion = DD1925B528CD591B00720036 /* MeshtasticDataModel v 11.xcdatamodel */; + currentVersion = DD769E0428D24E32001A3F05 /* MeshtasticDataModel v 12.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index b8009945..c7109a56 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -416,6 +416,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } } if (![FROMNUM_characteristic, FROMNUM_characteristic, TORADIO_characteristic].contains(nil)) { + sendWantConfig() } } @@ -640,6 +641,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.connectedPeripheral.subscribed = true peripherals.removeAll(where: { $0.peripheral.state == CBPeripheralState.disconnected }) // Config conplete returns so we don't read the characteristic again + + self.getChannel(channelIndex: 0, wantResponse: true) + + + return } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 0d87f123..8c5ea2ac 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModel v 11.xcdatamodel + MeshtasticDataModel v 12.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents index b1c358d7..9819e4ef 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -210,24 +210,4 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 12.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 12.xcdatamodel/contents new file mode 100644 index 00000000..41e85745 --- /dev/null +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 12.xcdatamodel/contents @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift index 6f576fe9..77c6dfd9 100644 --- a/Meshtastic/Views/Nodes/DeviceMetricsLog.swift +++ b/Meshtastic/Views/Nodes/DeviceMetricsLog.swift @@ -5,6 +5,7 @@ // Copyright(c) Garth Vander Houwen 7/7/22. // import SwiftUI +import Charts struct DeviceMetricsLog: View { @@ -15,11 +16,39 @@ struct DeviceMetricsLog: View { @State var exportString = "" var node: NodeInfoEntity + + struct MountPrice: Identifiable { + var id = UUID() + var mount: String + var value: Double + } var body: some View { NavigationStack { + let oneDayAgo = Calendar.current.date(byAdding: .day, value: -3, to: Date()) + + let data = node.telemetries!.filtered(using: NSPredicate(format: "metricsType == 0 && time !=nil && time >= %@", oneDayAgo! as CVarArg)) + + if data.count > 0 { + + GroupBox(label: Label("Battery Level Trend", systemImage: "battery.100")) { + + Chart(data.array as! [TelemetryEntity], id: \.self) { + LineMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + y: .value("Value", $0.batteryLevel) + ) + PointMark( + x: .value("Hour", $0.time!.formattedDate(format: "ha")), + y: .value("Value", $0.batteryLevel) + ) + } + .frame(height: 150) + } + } + ScrollView { Grid(alignment: .topLeading, horizontalSpacing: 2) { diff --git a/Meshtastic/Views/Settings/ShareChannel.swift b/Meshtastic/Views/Settings/ShareChannel.swift index 0badf12e..04ab6842 100644 --- a/Meshtastic/Views/Settings/ShareChannel.swift +++ b/Meshtastic/Views/Settings/ShareChannel.swift @@ -69,6 +69,17 @@ struct ShareChannel: View { alignment: .center ) + VStack { + ShareLink( + item: text, + preview: SharePreview( + "Meshtastic Channel Settings Link", + image: Image(systemName: "qrcode") + ) + ) + .font(.title3) + } + if node!.loRaConfig != nil { HStack { From c586a5cbd236ca848ab27a6310f05392ab0a47ca Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 15 Sep 2022 12:34:10 -0700 Subject: [PATCH 08/36] Dismiss keyboards --- Meshtastic.xcodeproj/project.pbxproj | 8 +- .../AppIcon.appiconset/1024.png | Bin 101425 -> 0 bytes .../AppIcon.appiconset/120-1.png | Bin 5663 -> 0 bytes .../AppIcon.appiconset/120.png | Bin 5663 -> 0 bytes .../AppIcon.appiconset/152.png | Bin 7129 -> 0 bytes .../AppIcon.appiconset/167.png | Bin 8370 -> 0 bytes .../AppIcon.appiconset/180.png | Bin 8799 -> 0 bytes .../Assets.xcassets/AppIcon.appiconset/20.png | Bin 858 -> 0 bytes .../Assets.xcassets/AppIcon.appiconset/29.png | Bin 1271 -> 0 bytes .../AppIcon.appiconset/40-1.png | Bin 1744 -> 0 bytes .../AppIcon.appiconset/40-2.png | Bin 1744 -> 0 bytes .../Assets.xcassets/AppIcon.appiconset/40.png | Bin 1744 -> 0 bytes .../AppIcon.appiconset/58-1.png | Bin 2609 -> 0 bytes .../Assets.xcassets/AppIcon.appiconset/58.png | Bin 2609 -> 0 bytes .../Assets.xcassets/AppIcon.appiconset/60.png | Bin 2609 -> 0 bytes .../Assets.xcassets/AppIcon.appiconset/76.png | Bin 3509 -> 0 bytes .../AppIcon.appiconset/80-1.png | Bin 3672 -> 0 bytes .../Assets.xcassets/AppIcon.appiconset/80.png | Bin 3672 -> 0 bytes .../Assets.xcassets/AppIcon.appiconset/87.png | Bin 3994 -> 0 bytes .../AppIcon.appiconset/Contents.json | 108 +----------------- .../AppIcon.appiconset/logo-3.png | Bin 0 -> 29551 bytes Meshtastic/Helpers/BLEManager.swift | 13 ++- Meshtastic/Helpers/MeshPackets.swift | 4 +- .../Config/Module/CannedMessagesConfig.swift | 1 + .../Settings/Config/Module/MQTTConfig.swift | 6 +- .../Config/Module/TelemetryConfig.swift | 2 +- .../Views/Settings/Config/NetworkConfig.swift | 1 + Meshtastic/Views/Settings/Settings.swift | 4 +- ...ShareChannel.swift => ShareChannels.swift} | 12 +- 29 files changed, 35 insertions(+), 124 deletions(-) delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/180.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/20.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/29.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/40-1.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/40-2.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/40.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/58-1.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/80.png delete mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/87.png create mode 100644 Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png rename Meshtastic/Views/Settings/{ShareChannel.swift => ShareChannels.swift} (88%) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index a1e85937..f7cfa598 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -43,7 +43,7 @@ DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; - DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannel.swift */; }; + DD6B85A828009258000ACD6B /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannels.swift */; }; DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; }; DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; }; DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; }; @@ -157,7 +157,7 @@ DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = ""; }; DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; - DD6B85A728009258000ACD6B /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = ""; }; + DD6B85A728009258000ACD6B /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; DD769E0428D24E32001A3F05 /* MeshtasticDataModel v 12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 12.xcdatamodel"; sourceTree = ""; }; @@ -306,7 +306,7 @@ DD3501882852FC3B000FC853 /* Settings.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, - DD6B85A728009258000ACD6B /* ShareChannel.swift */, + DD6B85A728009258000ACD6B /* ShareChannels.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, @@ -717,7 +717,7 @@ DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, DDB6ABE828B141AF00384BA1 /* WiFiModes.swift in Sources */, DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */, - DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */, + DD6B85A828009258000ACD6B /* ShareChannels.swift in Sources */, DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/1024.png deleted file mode 100644 index 403f4f2f687c1683f556acb5f53957ec1b557fff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101425 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 diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/120-1.png deleted file mode 100644 index 6e571624fee716e071b10584eecc24ed281944a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5663 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 diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/120.png deleted file mode 100644 index 6e571624fee716e071b10584eecc24ed281944a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5663 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 diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/152.png deleted file mode 100644 index a930e23a79c0a8b27f55df1fc27f7d2e7b0e4895..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7129 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 TtidDx8>?xk>ORFP!^8g{5MOOl diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/167.png deleted file mode 100644 index 35658530a0a6d6575db03ee9dad283128dea18ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8370 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%0D5lsJFc&hIA30 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 diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/58.png deleted file mode 100644 index ea3829e776aff59ab5903a144b78e8e5b208c74b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2609 zcmV-13eNS3P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91I-mmp1ONa40RR91IsgCw0Hdp`^Z)<}n@L1LRA>e5TWf3_XBEA(JG1L& z9B<;pYddlLtlPA1+U7+mLKG0HT2YWtRh1Tj5Ksh#s?djogerc~LZO9LMWBj;cvOfM zp_U>cQG|*hw$n7!G;!ir;`kA#iQjg-Z_c^nt=F5G@!A^@HvU%0&d$z!-#yIbTjZn^7y$rXUr-G(WIh!YNu|WJ3{yb{ z4QpKjB}a+Tq!%+^>x;Z_=EBRj-hjE`*#PRcGi&wyhDX<`=iKbx@Tl9)tkv@y9$l-R zbF=&Md-Q*_T)w1%`2mGD zNU_9B%%&?q(>u`%7p3k?*UQ!q?36gZbMq!Q$=V#0(N}xr?C-nvI2ldq_4Fu1fbV*L zo;1!2D80cQ?|=5FOdmTBPX?5N<@>|3^V8QzY<%9`qZFT@U*?Bqr00naJ!V`dLGvU8 z%`6Qd1V-i5PyVU%QjPN&s|IRQHXs-NdPW9+bqwAp(B1bJnbQ35LG`qk5Ya&d+Ve!a z%#X}Kj4Y-4rfVw1ED7jboO9s$f64eihBX}WNjs|pniddvto!kH4FIN=_;gh2?!8V* zVQgY1>P^|Qpp3k9QbzySr*WOQ7?VJ#P+FhEFk>rEY`0xSEeJOasZSWganqT3E$o%+iz{qV*`TL^vFT+7n8!68%jR->*I3%wE?l91FNC%%7Buxs9qwY zbJFwOb}=l!(vzxN_vIU;q`6!Ya~>+od<&HwdZ7zOpOyeNiK<+*t4!+e--`{yy;O0e zrPAG7r0$E?fx>o*vNxwq72Owr+3tJkxW_93YUez^^nU-aTs$)c(Xb>s9+8R<)k^gp zS18roC~MQ*l$owE>C2!YOUA|r_e&7Dlc|Nu%Q<48KPkNR{W~Ozupou!`X;4!f$m;H z=Y%w8ph{VIsmi&GoPcsJAL5a5IX^lJk+9Sol@O2c?Tt#aCEmzbSt$#OPrY?cPW||( zo%{iuqv@f8x&bT@NZIVcIjQF1Pc+N6kJRa!Tzh_aTJtY~=7@9o5|ET6O@?1Q0j2G? zDU01LtS*&?2lh#VC7OegX`|_xRLm~tSlm<0NZB2C@0QT}swF;^McMR?3-6qpsizUC zryyLSo}~&i|Bk$bvJp%nW0o799#wCkx18;rkiH)v=V3P)H(a>kYx{Iz9=jn`%A&wO z{amL^96qZ?#ik;{>Sx!zd$cIXrtBpK11Z-itg$yXi4fsn&)fTy-3Ty6(Jrsy2~_C> zkMy82iNXu)8Dmh{@W-2F`z`g}lwIg*CfY}2;HPhi305TGSsxTtb0myYPk68)ZFri( z43)}{HQ5x#{cIk(A8*qX=M7VfxAb&N-<7gu1u}#>_B`s@01Tyo6xEeMfFKSY`I73& z9x?${?tbi$Dl%i7@wW3bt+IJvcv;FWwFdL4I8preLj20KBxD{w|Ca$7eBrpQ!XC=+ z=utc8S$665?3TX3P{bJSIX9#(PL)k zyo{N-8En@sygn#{zkFN8D;Z57C7Gb?0qs}ajLDeB@lYv@we13>MZ<{sm<9hyKE8p? zYP-YTmjESY8BYU0V|$d5^IA}np_%q-q)Aye-f>Wro5Vrm=)qlI*dt}F+r000FfjWR zDNGyc??cIXMM%SmS%J3FX4>vlyet8lrrabN-qUP&A>BzzH)zv@9+Pxr$>x;vXb|tG zGWR`=uBLlTuV-9_Zmf|V_w2UCE8piou!T0zmL@h4q%>@#t!&b1yV{vW;YFatkvCQ)Vv)p&-kf{AULSoN$wUstcbc|>Pr@1wwY+3|LN31mcIq0L3SAZcdB!>N_DCps!n3<2+vuXSpPVk9Bu5M9zKL@00000NkvXXu0mjfJP-&3 diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/60.png deleted file mode 100644 index f384f426d1ccb771e9b08bac89954b8be2cb7b06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2609 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 To-NpX00000NkvXXu0mjfWm?|Q diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/76.png deleted file mode 100644 index bd7db1b26fe957d2644f8ead692eda02886f8ba6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3509 zcma)9XEYlC*G^EoR9mxZ3mRMPy+=^e*50xAYFay}sugO47pYZIBS@>MQ4OV}K@pqM z+N0G{HDc7Kf8Ot$@7MR^xz8QXIrp6VDZO$~JKJpyh!`Gq=ramW8cmAvuu7{dgiyua%~#!|@gnN`TH zduTQ~V?*^yWRlR&%5O);=i*e1N#!g8x6-SS3WMX;z_9U(X(@|CGRUF?~SqM>rgufkGuOy+LQsJBM5J+xFKN~**eyrA ztAxuw7FST*8Q8rtw0ZM)j{-Sv>yVzyuOqUIml>gRzmO(9_-%5>deSWkx4BFm>XKMp z9sy7gK~qRddmgM`b0U%x5v0RZ4Er7r`pR=NqgxBon`RM?3w^k9lyQyf! zdmtqDND`*8F{h<^j6OG8udj*BdYzyp{k?4dR@?m)k_Z2^vMuZ<7p(x->y0NCtlQA$ zB#OP8o{nV_yI6elm`6&+QB|PSAk^FW)VAKUaLu$;#I~d<_ z#r$oi2^%^boG7~1#oOHeu(SZri8Q&=9i`#_@wv&bm-%1c7;(I-ck(C{_E^zUa%8X^ z%Z8f<-uBk?>5s~Ya#ub30osM~`8p;UFG_7K)>Ij)swRdu{xn35|+8#)H*N+`3{ z^-0D=1uCZD?EWH^4{np)3_3;Zn{n`WOAeXO#Nz37j4OFqyz}pg@7xw%MvSg*#MeCc z&+lHIze-F7FYytmW^`*~LNku^k8m``i++!n-}2(gtDjHTa)xYLc{979qDZdVspAF7 zP3@6H+0d?Au184b>`W>QNl@H1dONe_e7C5NyPatO;rsQaWJia~+JsF0#ZoTG$U4P2MkX%50TyVk0ag^@kWNAOjj245ey3dHB0u|S#Ri*0X z5UKFL)z|P~A@FSC`>pr;xrDmu`!*s9LYl(lKRdxzwmtbnHX_I&h}DrxQ{xbqQqy|_ z)coC$Hqb*Lkyt^Q2|u_KLi(71ulzwQ4fxC_Yd6f#LL|27`u*MI>jY(Nq_edyB^}-) zlGSbP?VlBNb~oEm;k9sgq?Hd%1g2&WaWR!8Y@+}m8aYsL=)y5Q4hYwwoPUQqnD zTAGL`%X%CIoKbxYxp9@DD||@ygF&9tQN0xZmj_C)KE!Cy=``o7yX71$^G42WhMSex ziC0I+RcUAIhR!uuj8`kx^>hX}krn7=E|i>4mWadzyy9{>#uY$+FPI_xJ3uSUZsG!rhWy+Zr5LA z5c;~J+6as|wis%7Ay{NXwlb7*WEhA?qZFBc=k&UR6+G6#H7)n`jeG^@99vy1KWcb2 zz8-zvpi>t>B92(?4O!$a%ife%d*i&<)8QuA72=Wjg0q#%ydM4{79ZFSZ>c)QGwSt8 zV1Cb@u&JAck5nzKscc$EXw^+Tll0w1iSb-@+=pUAn@@$x)6OO^J&EBjQFZ{``%lHU;C?j@wo%>;yu zEj4W2#6xxEt7vf}TXEb?sG^NtLW*O(=ciP$rLrW<$(Rwh3b{ZQy3}3o#6@kyDcCa| z>^(Qbxg)lPJzX_)IOkHP5xhRN1mBW8NNZ`0C^i6Hk(E!D@^!f(ioBj)=UBzJ0@7^q zju+}76LEr(I(&L`kADsq@~{YZjkAw-sqcN!-Rw?$@`}lRaK>cH12%+bq<)P9dt@kt zwm>X3=Y6rp-P^smBP6??w{N0A=LOdp0##;sIy0%Rarj*E&ML6gboGDV zJ{nu~yaoQ-xE=HJ>VmVhSDBrtb*t9b{!qPg2i>Fv+CBWiA4E5AOZ3tTU9dqhcw#i6 z_@QPy&0%8?-*B@aeNaO$5Wh5y^${zMJ?L&!yJTmC&t<~|TF{D5_>k&PMq;KQ-CMc6 zTKR`Flb8ViqQpkqdjFbBgC3kLoR5A-JQdR>rsKh6vx^0ULy}sx73isBQO+ap`&j%w zCZ?=1BEd)^Iomb$r%_bpa*i2=dtR7DRw)MdfVFZAI4M{9Qp4O+Wug)Zx;fi|kg#ix zKm5Ku`6+vQohJQ~|3{xm(&-I|6#PHE+#Y5bLQm@rcV)Eqz;M-D6fmdB1m_g^+9R2< zp0J_Hp$x1GPh_*xwVVX5gOXX(Lqk_Bak)OTX; z)E1h=SHKWs!@M~zvgX)L5tasvf$y5K-H)-KzL&&$d)EvWJnWfP=?vFP%M(T8l^)z# zFP3<7zfLFv_b~u{I(=C~{H2(7;I}p{3WK0Gx=^WC8g(s_tIFuMZEssV+scDWPUbcy2=tnP6gZ#%8eadjH@id957^_@HMKOL}A?#F~rBkS(g!Ev;1-3A@p z94k*s8j0}*jD?hu0;mD0@nUq9K|wVSX<_8nK&l;q)SwE_snSYCKO)BM@P?1q&X6=V z%l8515kCG+qOk+ay{dw${A&VSNA2%|hjx4b zdBdWcYmIy8fDrhQ1VS#pcSPAI!+ZPgTQm9zO^Jcz@wyWwfxI>qO2wB@&0taw)1K#9 z#IHP$(u3yc>_148QB*at#f0}LkYuAEKImN=;4zm^_~0LK*LaKPLzv6i*xO3YdjS&x zrgHnFGFIW_auLVDOqqTHK_7O11`WnGCRTt!y=&3dxA#Zpn$AB4wX^%3_sDIBNyO|D z_R*3BkJfmcN33Qijh4$#vLfi2!t5!XB@+@H%}v-Y|1iF zRH;3YYS`*EIt{Zc2N52ZnFcv1B6=2ZpDPSfv~5KVZgE2w^dqf@^_)%O3ZiD65Y^j> z0+odO-|%1d!$o^aP0KxaYe-*~DU{`$*MJNN^a{&gr<8+H@3!aCs5HU`P$2c-c+uW6 z7xFgJq3aLO(bPQ}4bYf(Fgj%T64$<($zxq0)Wb+z5MTF}a*K8A;cy`~y z3y%Fc+RN024AbJWOCd}wzo#xmJR&%#YKH%iQX@D}&Oh0r#~Cm=X8&F$Q$tIGMm@Kr F{{cV9r9l7y diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/80-1.png deleted file mode 100644 index ed42f91e75ae9c71cb629aa2789f480687654580..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3672 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 diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json b/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json index 1cd4ae9f..937092d6 100644 --- a/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Meshtastic/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,111 +1,9 @@ { "images" : [ { - "filename" : "40-2.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "60.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "58.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "87.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "80.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "120-1.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "120.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "180.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "20.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "40-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "29.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "58-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "40.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "80-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "76.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "152.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "167.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", + "filename" : "logo-3.png", + "idiom" : "universal", + "platform" : "ios", "size" : "1024x1024" } ], diff --git a/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png b/Meshtastic/Assets.xcassets/AppIcon.appiconset/logo-3.png new file mode 100644 index 0000000000000000000000000000000000000000..4e2504d415d7c763b3e6cf4a92dee8ced4ae53f0 GIT binary patch literal 29551 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 0) } + .scrollDismissesKeyboard(.immediately) .disabled(bleManager.connectedPeripheral == nil) Button { diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 16c905ff..205bb957 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -80,8 +80,9 @@ struct MQTTConfig: View { hasChanges = true }) .foregroundColor(.gray) + .keyboardType(.default) } - .keyboardType(.default) + .autocorrectionDisabled() HStack { Label("Username", systemImage: "person.text.rectangle") @@ -109,6 +110,7 @@ struct MQTTConfig: View { .foregroundColor(.gray) } .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) HStack { @@ -137,8 +139,10 @@ struct MQTTConfig: View { .foregroundColor(.gray) } .keyboardType(.default) + .scrollDismissesKeyboard(.interactively) } } + .scrollDismissesKeyboard(.interactively) .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) Button { diff --git a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift index 70ac4680..f593e068 100644 --- a/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/TelemetryConfig.swift @@ -112,7 +112,7 @@ struct TelemetryConfig: View { Section(header: Text("Sensor Options")) { - Text("I2C Connected sensors will be detected automatically. Supported sensors are BMP280, BME280, BME680, MCP9808, INA219 and INA260.") + Text("Supported I2C Connected sensors will be detected automatically, sensors are BMP280, BME280, BME680, MCP9808, INA219 and INA260.") .font(.caption) Toggle(isOn: $environmentMeasurementEnabled) { diff --git a/Meshtastic/Views/Settings/Config/NetworkConfig.swift b/Meshtastic/Views/Settings/Config/NetworkConfig.swift index db25c040..3000d0ff 100644 --- a/Meshtastic/Views/Settings/Config/NetworkConfig.swift +++ b/Meshtastic/Views/Settings/Config/NetworkConfig.swift @@ -103,6 +103,7 @@ struct NetworkConfig: View { .keyboardType(.default) } } + .scrollDismissesKeyboard(.interactively) .disabled(!(node != nil && node!.myInfo?.hasWifi ?? false)) Button { diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index d0eb1e05..b998e532 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -38,11 +38,11 @@ struct Settings: View { Section("Radio Configuration") { NavigationLink { - ShareChannel(node: nodes.first(where: { $0.num == connectedNodeNum })) + ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum })) } label: { Image(systemName: "qrcode") .symbolRenderingMode(.hierarchical) - Text("Share Channel QR Code") + Text("Share Channels QR Code") } .disabled(bleManager.connectedPeripheral == nil) diff --git a/Meshtastic/Views/Settings/ShareChannel.swift b/Meshtastic/Views/Settings/ShareChannels.swift similarity index 88% rename from Meshtastic/Views/Settings/ShareChannel.swift rename to Meshtastic/Views/Settings/ShareChannels.swift index 04ab6842..ae221d22 100644 --- a/Meshtastic/Views/Settings/ShareChannel.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -31,7 +31,7 @@ struct QrCodeImage { } } -struct ShareChannel: View { +struct ShareChannels: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager @@ -73,10 +73,11 @@ struct ShareChannel: View { ShareLink( item: text, preview: SharePreview( - "Meshtastic Channel Settings Link", + "Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", image: Image(systemName: "qrcode") ) ) + .presentationDetents([.large, .large]) .font(.title3) } @@ -107,13 +108,6 @@ struct ShareChannel: View { .onAppear { self.bleManager.context = context - let i: UInt32 = 1; -// while i < 9 { // this should actually loop over MyNodeInfo.maxChannels to get all channels - print("requesting channel",i) - let resp = self.bleManager.getChannel(channelIndex: i, wantResponse: true) - print("resp from getChannel", resp) -// i+=1; -// } } } From dceb20b42f13ea4d47a79b5500be01ce3851c41c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 16 Sep 2022 05:35:42 -0700 Subject: [PATCH 09/36] Clean up node details view, use timer to get back channels --- Meshtastic/Helpers/BLEManager.swift | 35 +++++++++---------------- Meshtastic/Helpers/MeshPackets.swift | 5 ++++ Meshtastic/Views/Nodes/NodeDetail.swift | 15 ++++------- 3 files changed, 22 insertions(+), 33 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 65c05f49..9c3dd9eb 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -643,20 +643,20 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Config conplete returns so we don't read the characteristic again // Get all the channels - var i: UInt32 = 0; - while i < 8 { - // this should actually loop over MyNodeInfo.maxChannels to get all channels - //let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) in - print("requesting channel",i) + var i: UInt32 = 1; + + let timer = Timer.scheduledTimer(withTimeInterval: 2.0, + repeats: true) { timer in + if i == 9 { + timer.invalidate() // invalidate the timer + } else { + + print("requesting channel", i) let resp = self.getChannel(channelIndex: i, wantResponse: true) + i+=1; - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(3)) {} - //} - + } } - - - return } @@ -1345,22 +1345,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { - do { - - try context!.save() - if meshLoggingEnabled { MeshLogger.log("💾 Saved a Get Channel Request Admin Message for node: \(String(connectedPeripheral.num))") } + if meshLoggingEnabled { MeshLogger.log("🛎️ Send Get Channel Request Admin Message for node: \(String(connectedPeripheral.num))") } connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true - - } catch { - - context!.rollback() - - let nsError = error as NSError - print("💥 Error Inserting New Core Data MessageEntity: \(nsError)") - } } return false diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 6c84a589..17835d31 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1075,13 +1075,18 @@ func adminAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb print(try! powerConfig.jsonUTF8Data()) } else if let channel = try? Channel(serializedData: packet.decoded.payload) { + print(try! channel.jsonUTF8Data()) print("channel settings:", channel.settings) + } else if let channel = try? ChannelSettings(serializedData: packet.decoded.payload) { print(try! channel.jsonUTF8Data()) print("channel settings:", channel) } + print(try! packet.decoded.jsonUTF8Data()) + + if meshLogging { MeshLogger.log("ℹ️ MESH PACKET received for Admin App UNHANDLED \(try! packet.jsonString())") } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 41d5164f..292de1a8 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -71,14 +71,9 @@ struct NodeDetail: View { } else { HStack { - Image(node.user?.hwModel ?? "UNSET") - .resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(10) - .frame(width: bounds.size.width, height: bounds.size.height / 2.3) - .padding([.top], 40) + } - .offset( y:-40) + .padding([.top], 40) } ScrollView { @@ -159,7 +154,7 @@ struct NodeDetail: View { Text("AKA").font(.largeTitle) .foregroundColor(.gray).fixedSize() - .offset(y:20) + .offset(y:15) CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26) } .padding() @@ -172,8 +167,8 @@ struct NodeDetail: View { Image(hwModelString) .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 90, height: 90) + .aspectRatio(contentMode: .fill) + .frame(width: 200, height: 200) .cornerRadius(5) Text(String(hwModelString)) From a548253b07da1a690d344b87a66511389f9f977b Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 16 Sep 2022 08:50:37 -0700 Subject: [PATCH 10/36] Add max channels to peripheral --- Meshtastic/Helpers/BLEManager.swift | 3 ++- Meshtastic/Model/PeripheralModel.swift | 4 +++- Meshtastic/Views/Nodes/NodeDetail.swift | 7 ++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 9c3dd9eb..10b3c638 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -204,7 +204,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph peripheralName = name } - let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: peripheralName, shortName: last4Code, longName: peripheralName, lastFourCode: last4Code, firmwareVersion: "Unknown", rssi: RSSI.intValue, bitrate: nil, channelUtilization: nil, airTime: nil, lastUpdate: Date(), subscribed: false, peripheral: peripheral) + let newPeripheral = Peripheral(id: peripheral.identifier.uuidString, num: 0, name: peripheralName, shortName: last4Code, longName: peripheralName, lastFourCode: last4Code, firmwareVersion: "Unknown", rssi: RSSI.intValue, bitrate: nil, channelUtilization: nil, airTime: nil, maxChannels: 0, lastUpdate: Date(), subscribed: false, peripheral: peripheral) let peripheralIndex = peripherals.firstIndex(where: { $0.id == newPeripheral.id }) if peripheralIndex != nil && newPeripheral.peripheral.state != CBPeripheralState.connected { @@ -515,6 +515,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.connectedPeripheral.firmwareVersion = myInfo!.firmwareVersion ?? "Unknown" self.connectedPeripheral.name = myInfo!.bleName ?? "Unknown" self.connectedPeripheral.longName = myInfo!.bleName ?? "Unknown" + self.connectedPeripheral.maxChannels = myInfo!.maxChannels } } diff --git a/Meshtastic/Model/PeripheralModel.swift b/Meshtastic/Model/PeripheralModel.swift index cfdadc44..8587a795 100644 --- a/Meshtastic/Model/PeripheralModel.swift +++ b/Meshtastic/Model/PeripheralModel.swift @@ -13,11 +13,12 @@ struct Peripheral: Identifiable { var bitrate: Float? var channelUtilization: Float? var airTime: Float? + var maxChannels: Int32 var lastUpdate: Date var subscribed: Bool var peripheral: CBPeripheral - init(id: String, num: Int64, name: String, shortName: String, longName: String, lastFourCode: String, firmwareVersion: String, rssi: Int, bitrate: Float?, channelUtilization: Float?, airTime: Float?, lastUpdate: Date, subscribed: Bool, peripheral: CBPeripheral) { + init(id: String, num: Int64, name: String, shortName: String, longName: String, lastFourCode: String, firmwareVersion: String, rssi: Int, bitrate: Float?, channelUtilization: Float?, airTime: Float?, maxChannels: Int32, lastUpdate: Date, subscribed: Bool, peripheral: CBPeripheral) { self.id = id self.num = num self.name = name @@ -29,6 +30,7 @@ struct Peripheral: Identifiable { self.bitrate = bitrate self.channelUtilization = channelUtilization self.airTime = airTime + self.maxChannels = maxChannels self.lastUpdate = lastUpdate self.subscribed = subscribed self.peripheral = peripheral diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 292de1a8..20f79db5 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -147,6 +147,11 @@ struct NodeDetail: View { Divider() } + Image(hwModelString) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 200, height: 200) + .cornerRadius(5) HStack { @@ -154,7 +159,7 @@ struct NodeDetail: View { Text("AKA").font(.largeTitle) .foregroundColor(.gray).fixedSize() - .offset(y:15) + .offset(y:5) CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26) } .padding() From 25380b41406c674d5c86d14b9d787dbb2ea3896d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 17 Sep 2022 09:00:53 -0700 Subject: [PATCH 11/36] Proto updates, assorted fixes --- Meshtastic/Helpers/BLEManager.swift | 2 +- Meshtastic/Helpers/MeshPackets.swift | 1 - Meshtastic/Protobufs/module_config.pb.swift | 5 +++ Meshtastic/Views/Nodes/NodeDetail.swift | 43 ++++++++----------- .../Views/Settings/Config/LoRaConfig.swift | 1 + 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 10b3c638..e2f79c8d 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -29,7 +29,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph @Published var connectedPeripheral: Peripheral! @Published var lastConnectionError: String - @Published var connectedVersion: String + @State var connectedVersion: String @Published var isSwitchedOn: Bool = false @Published var isScanning: Bool = false diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 17835d31..09401138 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1230,7 +1230,6 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj let nsError = error as NSError print("💥 Error Saving ACK for message MessageID \(packet.id) Error: \(nsError)") } - } } diff --git a/Meshtastic/Protobufs/module_config.pb.swift b/Meshtastic/Protobufs/module_config.pb.swift index c9626648..832dc69a 100644 --- a/Meshtastic/Protobufs/module_config.pb.swift +++ b/Meshtastic/Protobufs/module_config.pb.swift @@ -330,6 +330,7 @@ struct ModuleConfig { case `default` // = 0 case simple // = 1 case proto // = 2 + case textmsg // = 3 case UNRECOGNIZED(Int) init() { @@ -341,6 +342,7 @@ struct ModuleConfig { case 0: self = .default case 1: self = .simple case 2: self = .proto + case 3: self = .textmsg default: self = .UNRECOGNIZED(rawValue) } } @@ -350,6 +352,7 @@ struct ModuleConfig { case .default: return 0 case .simple: return 1 case .proto: return 2 + case .textmsg: return 3 case .UNRECOGNIZED(let i): return i } } @@ -649,6 +652,7 @@ extension ModuleConfig.SerialConfig.Serial_Mode: CaseIterable { .default, .simple, .proto, + .textmsg, ] } @@ -1001,6 +1005,7 @@ extension ModuleConfig.SerialConfig.Serial_Mode: SwiftProtobuf._ProtoNameProvidi 0: .same(proto: "DEFAULT"), 1: .same(proto: "SIMPLE"), 2: .same(proto: "PROTO"), + 3: .same(proto: "TEXTMSG"), ] } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 20f79db5..405a8777 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -12,10 +12,12 @@ struct NodeDetail: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager + @State private var showingDetailsPopover = false + @State var initialLoad: Bool = true @State var satsInView = 0 - @State private var isPresentingShutdownConfirm: Bool = false - @State private var isPresentingRebootConfirm: Bool = false + @State private var showingShutdownConfirm: Bool = false + @State private var showingRebootConfirm: Bool = false var node: NodeInfoEntity @@ -79,15 +81,14 @@ struct NodeDetail: View { ScrollView { if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { - - Divider() + HStack { if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { Button(action: { - isPresentingShutdownConfirm = true + showingShutdownConfirm = true }) { Label("Power Off", systemImage: "power") @@ -98,7 +99,7 @@ struct NodeDetail: View { .padding() .confirmationDialog( "Are you sure?", - isPresented: $isPresentingShutdownConfirm + isPresented: $showingShutdownConfirm ) { Button("Shutdown Node?", role: .destructive) { @@ -112,7 +113,7 @@ struct NodeDetail: View { Button(action: { - isPresentingRebootConfirm = true + showingRebootConfirm = true }) { @@ -125,7 +126,7 @@ struct NodeDetail: View { .confirmationDialog( "Are you sure?", - isPresented: $isPresentingRebootConfirm + isPresented: $showingRebootConfirm ) { Button("Reboot Node?", role: .destructive) { @@ -140,18 +141,10 @@ struct NodeDetail: View { .padding(5) } + Divider() + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { - - // Add a divider if there is no map - if (node.positions?.count ?? 0) == 0 { - - Divider() - } - Image(hwModelString) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 200, height: 200) - .cornerRadius(5) + HStack { @@ -229,10 +222,15 @@ struct NodeDetail: View { } .padding() } - Divider() + } .padding() + .onLongPressGesture(minimumDuration: 2) { + + print("Long pressed!") + } + Divider() HStack(alignment: .center) { VStack { @@ -364,8 +362,6 @@ struct NodeDetail: View { } .padding(4) - Divider() - HStack(alignment: .center) { VStack { HStack { @@ -406,7 +402,6 @@ struct NodeDetail: View { if (node.positions?.count ?? 0) > 0 { - Divider() NavigationLink { PositionLog(node: node) } label: { @@ -419,11 +414,11 @@ struct NodeDetail: View { .font(.title3) } .fixedSize(horizontal: false, vertical: true) + Divider() } if (node.telemetries?.count ?? 0) > 0 { - Divider() NavigationLink { DeviceMetricsLog(node: node) } label: { diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 2e63c168..89e53be7 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -87,6 +87,7 @@ struct LoRaConfig: View { lc.hopLimit = UInt32(hopLimit) lc.region = RegionCodes(rawValue: region)!.protoEnumValue() lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() + lc.txEnabled = true let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: node!.user!, toUser: node!.user!) From 3bc6c09bddb27e5c9b44e0a781ad50b028fa9ce7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 17 Sep 2022 09:05:49 -0700 Subject: [PATCH 12/36] dont turn tx on automatically --- Meshtastic/Views/Settings/Config/LoRaConfig.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 89e53be7..e660a94b 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -87,7 +87,7 @@ struct LoRaConfig: View { lc.hopLimit = UInt32(hopLimit) lc.region = RegionCodes(rawValue: region)!.protoEnumValue() lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() - lc.txEnabled = true + //lc.txEnabled = true let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: node!.user!, toUser: node!.user!) From 56e70150a1292e24df780e8425edd20af5ff9c4c Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 17 Sep 2022 12:41:27 -0700 Subject: [PATCH 13/36] Update text on mqtt page --- Meshtastic/Views/Messages/UserMessageList.swift | 2 +- Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Messages/UserMessageList.swift b/Meshtastic/Views/Messages/UserMessageList.swift index f8c164fe..dc2a8127 100644 --- a/Meshtastic/Views/Messages/UserMessageList.swift +++ b/Meshtastic/Views/Messages/UserMessageList.swift @@ -359,7 +359,7 @@ struct UserMessageList: View { .listRowSeparator(.hidden) } } - .scrollDismissesKeyboard(.interactively) + .scrollDismissesKeyboard(.immediately) .onAppear(perform: { self.bleManager.context = context diff --git a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift index 205bb957..4182c287 100644 --- a/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/MQTTConfig.swift @@ -163,7 +163,7 @@ struct MQTTConfig: View { "Are you sure?", isPresented: $isPresentingSaveConfirm ) { - Button("Save WiFI Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { + Button("Save MQTT Config to \(bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.longName : "Unknown")?") { var mqtt = ModuleConfig.MQTTConfig() mqtt.enabled = self.enabled From 3246459f31c1fef0f62096e5114434b21153dab7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 18 Sep 2022 08:18:42 -0700 Subject: [PATCH 14/36] Delete bad data migration and start over --- Meshtastic.xcodeproj/project.pbxproj | 3 +- .../Meshtastic.xcdatamodeld/.xccurrentversion | 2 +- .../contents | 233 ------------------ 3 files changed, 2 insertions(+), 236 deletions(-) delete mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 12.xcdatamodel/contents diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f7cfa598..f2871112 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1168,7 +1168,6 @@ DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */ = { isa = XCVersionGroup; children = ( - DD769E0428D24E32001A3F05 /* MeshtasticDataModel v 12.xcdatamodel */, DD1925B528CD591B00720036 /* MeshtasticDataModel v 11.xcdatamodel */, DD2160AD28C5536B00C17253 /* MeshtasticDataModel v 10.xcdatamodel */, DD5929A528C0F292003DB21D /* MeshtasticDataModel v 9.xcdatamodel */, @@ -1181,7 +1180,7 @@ DD45C77427BD4EF80011784F /* MeshtasticDataModel v2.xcdatamodel */, DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */, ); - currentVersion = DD769E0428D24E32001A3F05 /* MeshtasticDataModel v 12.xcdatamodel */; + currentVersion = DD1925B528CD591B00720036 /* MeshtasticDataModel v 11.xcdatamodel */; name = Meshtastic.xcdatamodeld; path = Meshtastic/Meshtastic.xcdatamodeld; sourceTree = ""; diff --git a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion index 8c5ea2ac..0d87f123 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion +++ b/Meshtastic/Meshtastic.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MeshtasticDataModel v 12.xcdatamodel + MeshtasticDataModel v 11.xcdatamodel diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 12.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 12.xcdatamodel/contents deleted file mode 100644 index 41e85745..00000000 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 12.xcdatamodel/contents +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From a3d20585a114f85230debeef640f4da8e966d7fe Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 19 Sep 2022 15:47:21 -0700 Subject: [PATCH 15/36] IOS 16 updates --- Meshtastic.xcodeproj/project.pbxproj | 5 ++- Meshtastic/Helpers/BLEManager.swift | 5 +-- .../Helpers/Messages/MessageTemplate.swift | 38 +++++++++++++++++++ .../Views/Settings/Config/LoRaConfig.swift | 3 +- 4 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 Meshtastic/Views/Helpers/Messages/MessageTemplate.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 068225ef..9e9a92c9 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; }; DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */; }; + DDA1C48728D82022009933EC /* MessageTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48628D82022009933EC /* MessageTemplate.swift */; }; 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 */; }; @@ -160,7 +161,6 @@ DD6B85A728009258000ACD6B /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; - DD769E0428D24E32001A3F05 /* MeshtasticDataModel v 12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 12.xcdatamodel"; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = ""; }; @@ -181,6 +181,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 = ""; }; DDA1C48528D77310009933EC /* MeshtasticDataModel v 12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 12.xcdatamodel"; sourceTree = ""; }; + DDA1C48628D82022009933EC /* MessageTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTemplate.swift; 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 = ""; }; @@ -275,6 +276,7 @@ DD2160AC28C5019400C17253 /* Messages */ = { isa = PBXGroup; children = ( + DDA1C48628D82022009933EC /* MessageTemplate.swift */, ); path = Messages; sourceTree = ""; @@ -712,6 +714,7 @@ DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */, + DDA1C48728D82022009933EC /* MessageTemplate.swift in Sources */, DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */, DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index e2f79c8d..d16331bf 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -646,9 +646,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Get all the channels var i: UInt32 = 1; - let timer = Timer.scheduledTimer(withTimeInterval: 2.0, + Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { timer in - if i == 9 { + if i == self.connectedPeripheral.maxChannels { timer.invalidate() // invalidate the timer } else { @@ -1345,7 +1345,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph let binaryData: Data = try! toRadio.serializedData() if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { - if meshLoggingEnabled { MeshLogger.log("🛎️ Send Get Channel Request Admin Message for node: \(String(connectedPeripheral.num))") } diff --git a/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift b/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift new file mode 100644 index 00000000..5cb63190 --- /dev/null +++ b/Meshtastic/Views/Helpers/Messages/MessageTemplate.swift @@ -0,0 +1,38 @@ +// +// MessageTemplate.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 9/18/22. +// +import SwiftUI + +struct MessageTemplate: View { + + var user: UserEntity + var message: MessageEntity + var messageReply: MessageEntity? + + var body: some View { + + // Display the message being replied to and the arrow + if message.replyID > 0 { + + HStack { + + Text(messageReply?.messagePayload ?? "EMPTY MESSAGE").foregroundColor(.blue).font(.caption2) + .padding(10) + .overlay( + RoundedRectangle(cornerRadius: 18) + .stroke(Color.blue, lineWidth: 0.5) + ) + Image(systemName: "arrowshape.turn.up.left.fill") + .symbolRenderingMode(.hierarchical) + .imageScale(.large).foregroundColor(.blue) + .padding(.trailing) + } + } + + // Message + + } +} diff --git a/Meshtastic/Views/Settings/Config/LoRaConfig.swift b/Meshtastic/Views/Settings/Config/LoRaConfig.swift index 2ea1f756..d86e4846 100644 --- a/Meshtastic/Views/Settings/Config/LoRaConfig.swift +++ b/Meshtastic/Views/Settings/Config/LoRaConfig.swift @@ -89,7 +89,8 @@ struct LoRaConfig: View { lc.hopLimit = UInt32(hopLimit) lc.region = RegionCodes(rawValue: region)!.protoEnumValue() lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue() - //lc.txEnabled = true + lc.usePreset = true + lc.txEnabled = true let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: node!.user!, toUser: node!.user!) From 5554d6231f92b933af7d8ba358c055fe51bd4062 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Mon, 19 Sep 2022 23:21:05 -0700 Subject: [PATCH 16/36] Serialize channel responses --- Meshtastic/Helpers/BLEManager.swift | 11 +++----- Meshtastic/Helpers/MeshPackets.swift | 41 +++------------------------- 2 files changed, 8 insertions(+), 44 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index d16331bf..6b7994c4 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -646,15 +646,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Get all the channels var i: UInt32 = 1; - Timer.scheduledTimer(withTimeInterval: 0.25, + Timer.scheduledTimer(withTimeInterval: 0.4, repeats: true) { timer in - if i == self.connectedPeripheral.maxChannels { + if i == (self.connectedPeripheral.maxChannels + 1) { timer.invalidate() // invalidate the timer } else { - print("requesting channel", i) - let resp = self.getChannel(channelIndex: i, wantResponse: true) - + _ = self.getChannel(channelIndex: i, wantResponse: true) i+=1; } } @@ -1323,7 +1321,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph var adminPacket = AdminMessage() adminPacket.getChannelRequest = channelIndex - var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(connectedPeripheral.num) meshPacket.from = 0 //UInt32(connectedPeripheral.num) @@ -1346,7 +1343,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph if connectedPeripheral!.peripheral.state == CBPeripheralState.connected { - if meshLoggingEnabled { MeshLogger.log("🛎️ Send Get Channel Request Admin Message for node: \(String(connectedPeripheral.num))") } + if meshLoggingEnabled { MeshLogger.log("🛎️ Send Get Channel \(channelIndex) Request Admin Message for node: \(String(connectedPeripheral.num))") } connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) return true diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 1c290ba8..d86c7aa7 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1064,47 +1064,14 @@ func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManage } func adminAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { - - if let deviceConfig = try? Config.DeviceConfig(serializedData: packet.decoded.payload) { + + if let channelMessage = try? Channel(serializedData: packet.decoded.payload) { - print(try! deviceConfig.jsonString()) - - } else if let displayConfig = try? Config.DisplayConfig(serializedData: packet.decoded.payload) { - - print(try! displayConfig.jsonUTF8Data()) - print(displayConfig.gpsFormat) - - } else if let loraConfig = try? Config.LoRaConfig(serializedData: packet.decoded.payload) { - - print(try! loraConfig.jsonUTF8Data()) - print(loraConfig.region) - - } else if let positionConfig = try? Config.PositionConfig(serializedData: packet.decoded.payload) { - - print(try! positionConfig.jsonUTF8Data()) - print(positionConfig.positionBroadcastSecs) - - } else if let powerConfig = try? Config.PowerConfig(serializedData: packet.decoded.payload) { - - print(try! powerConfig.jsonUTF8Data()) - - } else if let channel = try? Channel(serializedData: packet.decoded.payload) { - - print(try! channel.jsonUTF8Data()) - print("channel settings:", channel.settings) - - } else if let channel = try? ChannelSettings(serializedData: packet.decoded.payload) { - print(try! channel.jsonUTF8Data()) - print("channel settings:", channel) + if meshLogging { MeshLogger.log("ℹ️ Channel Message received for Admin App \(try! channelMessage.jsonString())") } } - print(try! packet.decoded.jsonUTF8Data()) - - - - if meshLogging { MeshLogger.log("ℹ️ MESH PACKET received for Admin App UNHANDLED \(try! packet.jsonString())") } - } + func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) { let fetchNodePositionRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") From 7bb3125cf67d5bd21216448e56564164a059bd50 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 24 Sep 2022 13:32:35 -0700 Subject: [PATCH 17/36] Rename view to aid merge --- Meshtastic.xcodeproj/project.pbxproj | 8 ++++---- .../Settings/{ShareChannel.swift => ShareChannels.swift} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename Meshtastic/Views/Settings/{ShareChannel.swift => ShareChannels.swift} (100%) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index b112b121..d0e7e0ff 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -43,7 +43,7 @@ DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; - DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannel.swift */; }; + DD6B85A828009258000ACD6B /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannels.swift */; }; DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* LocationHistory.swift */; }; DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; }; DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; }; @@ -157,7 +157,7 @@ DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = ""; }; DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; - DD6B85A728009258000ACD6B /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = ""; }; + DD6B85A728009258000ACD6B /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD73FD1028750779000852D6 /* LocationHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHistory.swift; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = ""; }; @@ -300,7 +300,7 @@ DD3501882852FC3B000FC853 /* Settings.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, - DD6B85A728009258000ACD6B /* ShareChannel.swift */, + DD6B85A728009258000ACD6B /* ShareChannels.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, @@ -711,7 +711,7 @@ DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, DDB6ABE828B141AF00384BA1 /* WiFiModes.swift in Sources */, DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */, - DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */, + DD6B85A828009258000ACD6B /* ShareChannels.swift in Sources */, DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, diff --git a/Meshtastic/Views/Settings/ShareChannel.swift b/Meshtastic/Views/Settings/ShareChannels.swift similarity index 100% rename from Meshtastic/Views/Settings/ShareChannel.swift rename to Meshtastic/Views/Settings/ShareChannels.swift From 087404b6202c278842f923288c73d5c3fdb99cef Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 24 Sep 2022 13:36:41 -0700 Subject: [PATCH 18/36] Delete deleted file from project --- Meshtastic.xcodeproj/project.pbxproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index b21038ea..34f51429 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -62,7 +62,6 @@ DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; }; DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */; }; - DDA1C48728D82022009933EC /* MessageTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48628D82022009933EC /* MessageTemplate.swift */; }; 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 */; }; From 469e41dc2fa6912f8d013b32b7c3acdfb2d15f50 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 25 Sep 2022 15:42:45 -0700 Subject: [PATCH 19/36] Add missing bracket from merge --- Meshtastic/Views/Settings/ShareChannels.swift | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 74bf5b40..255ec808 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -51,7 +51,7 @@ struct ShareChannels: View { let smallest = min(bounds.size.width, bounds.size.height) ScrollView { - + VStack { Text("Scan the QR code below with the Apple or Android device you would like to share your channel settings with.") .fixedSize(horizontal: false, vertical: true) @@ -89,23 +89,24 @@ struct ShareChannels: View { Text("Channel: \(channel.index) Name: \(channel.name ?? "")") } } + } + .frame(width: bounds.size.width, height: bounds.size.height) } - .frame(width: bounds.size.width, height: bounds.size.height) + } + .navigationTitle("Share Channel") + .navigationBarTitleDisplayMode(.automatic) + .navigationBarItems(trailing: + + ZStack { + + ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") + }) + .onAppear { + + self.bleManager.context = context } } - .navigationTitle("Share Channel") - .navigationBarTitleDisplayMode(.automatic) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - }) - .onAppear { - - self.bleManager.context = context - } + .navigationViewStyle(StackNavigationViewStyle()) } - .navigationViewStyle(StackNavigationViewStyle()) } } From 2f3f6c11b74ecba5b86ddaec87bee7974368ee48 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 25 Sep 2022 16:31:14 -0700 Subject: [PATCH 20/36] Add back sharelink --- Meshtastic/Views/Settings/ShareChannels.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 255ec808..95501702 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -69,6 +69,18 @@ struct ShareChannels: View { alignment: .center ) + VStack { + ShareLink( + item: text, + preview: SharePreview( + "Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", + image: Image(systemName: "qrcode") + ) + ) + .presentationDetents([.large, .large]) + .font(.title3) + } + if node != nil && node!.loRaConfig != nil { HStack { From e324de48583c0ba3fd24b8acb3ea93a6a066ceef Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 27 Sep 2022 07:10:31 -0700 Subject: [PATCH 21/36] Delete Share channels for merge --- Meshtastic.xcodeproj/project.pbxproj | 13 -- Meshtastic/Views/Settings/Settings.swift | 16 +-- Meshtastic/Views/Settings/ShareChannels.swift | 124 ------------------ 3 files changed, 8 insertions(+), 145 deletions(-) delete mode 100644 Meshtastic/Views/Settings/ShareChannels.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6fb06ca4..438dca3c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -43,7 +43,6 @@ DD6193752862F6E600E59241 /* ExternalNotificationConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */; }; DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; }; DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; }; - DD6B85A828009258000ACD6B /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannels.swift */; }; DD73FD1128750779000852D6 /* PositionLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* PositionLog.swift */; }; DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */; }; DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; }; @@ -158,7 +157,6 @@ DD6193742862F6E600E59241 /* ExternalNotificationConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalNotificationConfig.swift; sourceTree = ""; }; DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = ""; }; DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = ""; }; - DD6B85A728009258000ACD6B /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD73FD1028750779000852D6 /* PositionLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionLog.swift; sourceTree = ""; }; DD769E0228D18BF0001A3F05 /* DeviceMetricsLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetricsLog.swift; sourceTree = ""; }; DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = ""; }; @@ -275,14 +273,6 @@ path = Custom; sourceTree = ""; }; - DD2160AC28C5019400C17253 /* Messages */ = { - isa = PBXGroup; - children = ( - DDA1C48628D82022009933EC /* MessageTemplate.swift */, - ); - path = Messages; - sourceTree = ""; - }; DD47E3CA26F0E50300029299 /* Nodes */ = { isa = PBXGroup; children = ( @@ -311,7 +301,6 @@ DD3501882852FC3B000FC853 /* Settings.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, - DD6B85A728009258000ACD6B /* ShareChannels.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, @@ -716,14 +705,12 @@ DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */, DD3501892852FC3B000FC853 /* Settings.swift in Sources */, DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */, - DDA1C48728D82022009933EC /* MessageTemplate.swift in Sources */, DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */, DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, DDB6ABE828B141AF00384BA1 /* WiFiModes.swift in Sources */, DD4F23CD28779A3C001D37CB /* EnvironmentMetricsLog.swift in Sources */, - DD6B85A828009258000ACD6B /* ShareChannels.swift in Sources */, DDB6ABD628AE742000384BA1 /* BluetoothConfig.swift in Sources */, DD769E0328D18BF1001A3F05 /* DeviceMetricsLog.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, diff --git a/Meshtastic/Views/Settings/Settings.swift b/Meshtastic/Views/Settings/Settings.swift index b998e532..615c4985 100644 --- a/Meshtastic/Views/Settings/Settings.swift +++ b/Meshtastic/Views/Settings/Settings.swift @@ -37,14 +37,14 @@ struct Settings: View { Section("Radio Configuration") { - NavigationLink { - ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum })) - } label: { - Image(systemName: "qrcode") - .symbolRenderingMode(.hierarchical) - Text("Share Channels QR Code") - } - .disabled(bleManager.connectedPeripheral == nil) +// NavigationLink { +// ShareChannels(node: nodes.first(where: { $0.num == connectedNodeNum })) +// } label: { +// Image(systemName: "qrcode") +// .symbolRenderingMode(.hierarchical) +// Text("Share Channels QR Code") +// } +// .disabled(bleManager.connectedPeripheral == nil) NavigationLink { UserConfig(node: nodes.first(where: { $0.num == connectedNodeNum })) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift deleted file mode 100644 index 95501702..00000000 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// ShareChannel.swift -// MeshtasticApple -// -// Created by Garth Vander Houwen on 4/8/22. -// -import SwiftUI -import CoreData -import CoreImage.CIFilterBuiltins - - -struct QrCodeImage { - let context = CIContext() - - func generateQRCode(from text: String) -> UIImage { - var qrImage = UIImage(systemName: "xmark.circle") ?? UIImage() - let data = Data(text.utf8) - let filter = CIFilter.qrCodeGenerator() - filter.setValue(data, forKey: "inputMessage") - - let transform = CGAffineTransform(scaleX: 20, y: 20) - if let outputImage = filter.outputImage?.transformed(by: transform) { - if let image = context.createCGImage( - outputImage, - from: outputImage.extent) { - qrImage = UIImage(cgImage: image) - } - } - - return qrImage - } -} - -struct ShareChannels: View { - - @Environment(\.managedObjectContext) var context - @EnvironmentObject var bleManager: BLEManager - @EnvironmentObject var userSettings: UserSettings - - var node: NodeInfoEntity? - - @State private var text = "https://meshtastic.org/E/#test" - var qrCodeImage = QrCodeImage() - - var body: some View { - - VStack { - - GeometryReader { bounds in - - let smallest = min(bounds.size.width, bounds.size.height) - - ScrollView { - - VStack { - Text("Scan the QR code below with the Apple or Android device you would like to share your channel settings with.") - .fixedSize(horizontal: false, vertical: true) - .font(.callout) - - let image = qrCodeImage.generateQRCode(from: text) - Image(uiImage: image) - .resizable() - .scaledToFit() - .frame( - minWidth: smallest * 0.8, - maxWidth: smallest * 0.8, - minHeight: smallest * 0.8, - maxHeight: smallest * 0.8, - alignment: .center - ) - - VStack { - ShareLink( - item: text, - preview: SharePreview( - "Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", - image: Image(systemName: "qrcode") - ) - ) - .presentationDetents([.large, .large]) - .font(.title3) - } - - if node != nil && node!.loRaConfig != nil { - - HStack { - - let preset = ModemPresets(rawValue: Int(node!.loRaConfig!.modemPreset)) - Text("Modem Preset \(preset!.description)").font(.title3) - } - } - VStack { - - Text("Number of Channels: \(node!.myInfo!.maxChannels)").font(.title2) - - ForEach(node!.myInfo!.channels?.array.sorted(by: { ($0 as! ChannelEntity).index < ($1 as! ChannelEntity).index }) as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in - - VStack { - - - Text("Channel: \(channel.index) Name: \(channel.name ?? "")") - } - } - } - .frame(width: bounds.size.width, height: bounds.size.height) - } - } - .navigationTitle("Share Channel") - .navigationBarTitleDisplayMode(.automatic) - .navigationBarItems(trailing: - - ZStack { - - ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") - }) - .onAppear { - - self.bleManager.context = context - } - } - .navigationViewStyle(StackNavigationViewStyle()) - } - } -} From d892af07b1380233ec3537d36a2fba9774ffded7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 27 Sep 2022 21:47:13 -0700 Subject: [PATCH 22/36] Delete weird version --- .../contents | 213 ------------------ 1 file changed, 213 deletions(-) delete mode 100644 Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents deleted file mode 100644 index 9819e4ef..00000000 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 11.xcdatamodel/contents +++ /dev/null @@ -1,213 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 401423f6066a86f87e9694c61c1fa99d7c31fc44 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 27 Sep 2022 22:42:45 -0700 Subject: [PATCH 23/36] Sharelink for qr code --- Meshtastic.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 069efd23..f6e0d27e 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -290,11 +290,11 @@ DD3501882852FC3B000FC853 /* Settings.swift */, DD4A911D2708C65400501B7E /* AppSettings.swift */, DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */, + DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, DD86D40B287F401000BAEB7A /* SaveChannelQRCode.swift */, DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */, DD0F791A28713C8A00A6FDAD /* AdminMessageList.swift */, DD61937A2863876A00E59241 /* Config */, - DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */, ); path = Settings; sourceTree = ""; From ef6d28bfadaa49cecc797e59c004b23201111f27 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 28 Sep 2022 15:44:42 -0700 Subject: [PATCH 24/36] Battery gauge --- Meshtastic.xcodeproj/project.pbxproj | 4 ++ Meshtastic/Views/Bluetooth/Connect.swift | 2 +- Meshtastic/Views/Helpers/BatteryGauge.swift | 33 ++++++++++++ Meshtastic/Views/Nodes/NodeDetail.swift | 52 +++++++------------ Meshtastic/Views/Settings/ShareChannels.swift | 12 +++++ 5 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 Meshtastic/Views/Helpers/BatteryGauge.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f6e0d27e..797531e1 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD35018A2852FC79000FC853 /* UserSettings.swift */; }; DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */; }; DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; }; + DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */; }; DD4033C228B286B70096A444 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4033C128B286B70096A444 /* Onboarding.swift */; }; DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582528582E9B009B0E59 /* DeviceConfig.swift */; }; DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; }; @@ -137,6 +138,7 @@ DD35018A2852FC79000FC853 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = ""; }; DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = ""; }; + DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = ""; }; DD4033C128B286B70096A444 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = ""; }; DD41582528582E9B009B0E59 /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = ""; }; DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = ""; }; @@ -503,6 +505,7 @@ DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */, DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */, DDB6ABDA28B0AC6000384BA1 /* DistanceText.swift */, + DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */, ); path = Helpers; sourceTree = ""; @@ -716,6 +719,7 @@ DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */, DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */, DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */, + DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */, DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */, DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */, DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */, diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index d5eb57c1..4cf30148 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -184,7 +184,7 @@ struct Connect: View { } } - HStack(alignment: .center) { + HStack(alignment: .center) { Spacer() diff --git a/Meshtastic/Views/Helpers/BatteryGauge.swift b/Meshtastic/Views/Helpers/BatteryGauge.swift new file mode 100644 index 00000000..c6a52762 --- /dev/null +++ b/Meshtastic/Views/Helpers/BatteryGauge.swift @@ -0,0 +1,33 @@ +// +// BatteryGauge.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 9/28/22. +// + +import SwiftUI +import Charts + +struct BatteryGauge: View { + + @State var batteryLevel = 64.0 + + private let minValue = 1.0 + private let maxValue = 100.00 + + let gradient = Gradient(colors: [.red, .yellow, .green]) + + var body: some View { + VStack { + Gauge(value: batteryLevel, in: minValue...maxValue) { + Label("Battery Level %", systemImage: "battery.0") + } currentValueLabel: { + Text(Int(batteryLevel), format: .percent) + } + .tint(gradient) + } + .gaugeStyle(.accessoryCircular) + + .padding() + } +} diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 405a8777..b773824c 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -191,7 +191,11 @@ struct NodeDetail: View { .font(.largeTitle) .foregroundColor(.gray) .fixedSize() + + } + + } if node.telemetries?.count ?? 0 >= 1 { @@ -199,23 +203,15 @@ struct NodeDetail: View { let mostRecent = node.telemetries?.lastObject as! TelemetryEntity Divider() - + VStack(alignment: .center) { - - BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .largeTitle, color: .accentColor) - .padding(.bottom, 10) - if mostRecent.batteryLevel > 0 { - Text(String(mostRecent.batteryLevel) + "%") - .font(.largeTitle) - .frame(width: 100) - .foregroundColor(.gray) - .fixedSize() - } + BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel)) + if mostRecent.voltage > 0 { - + Text(String(format: "%.2f", mostRecent.voltage) + " V") - .font(.largeTitle) + .font(.title) .foregroundColor(.gray) .fixedSize() } @@ -225,10 +221,6 @@ struct NodeDetail: View { } .padding() - .onLongPressGesture(minimumDuration: 2) { - - print("Long pressed!") - } Divider() HStack(alignment: .center) { @@ -291,11 +283,9 @@ struct NodeDetail: View { HStack { VStack(alignment: .center) { - Text("AKA").font(.title2).fixedSize() + CircleText(text: node.user?.shortName ?? "???", color: .accentColor) - .offset(y: 10) } - .padding(5) Divider() @@ -340,29 +330,24 @@ struct NodeDetail: View { VStack(alignment: .center) { - BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor) - .padding(.bottom) + BatteryGauge(batteryLevel: Double(mostRecent.batteryLevel)) - if mostRecent.batteryLevel > 0 { - Text(String(mostRecent.batteryLevel) + "%") - .font(.title3) - .foregroundColor(.gray) - .fixedSize() - } if mostRecent.voltage > 0 { Text(String(format: "%.2f", mostRecent.voltage) + " V") - .font(.title3) + .font(.callout) .foregroundColor(.gray) .fixedSize() + .offset(y: -25) } + } - .padding(5) } } - .padding(4) - + Divider() HStack(alignment: .center) { + + VStack { HStack { Image(systemName: "person") @@ -396,12 +381,15 @@ struct NodeDetail: View { Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray) } .padding([.bottom], 0) + Divider() } VStack { if (node.positions?.count ?? 0) > 0 { + + NavigationLink { PositionLog(node: node) } label: { diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 82dbb747..3fda36af 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -67,6 +67,18 @@ struct ShareChannels: View { alignment: .center ) + VStack { + ShareLink( + item: text, + preview: SharePreview( + "Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", + image: Image(systemName: "qrcode") + ) + ) + .presentationDetents([.large, .large]) + .font(.title3) + } + if node != nil && node!.loRaConfig != nil { HStack { From e4894232e7a99231f7416916322559357b5df861 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 28 Sep 2022 16:17:10 -0700 Subject: [PATCH 25/36] Improve battery gauge --- Meshtastic/Views/Helpers/BatteryGauge.swift | 38 +++++++++++++++------ Meshtastic/Views/Nodes/NodeDetail.swift | 1 - 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Meshtastic/Views/Helpers/BatteryGauge.swift b/Meshtastic/Views/Helpers/BatteryGauge.swift index c6a52762..7c2448d9 100644 --- a/Meshtastic/Views/Helpers/BatteryGauge.swift +++ b/Meshtastic/Views/Helpers/BatteryGauge.swift @@ -10,24 +10,40 @@ import Charts struct BatteryGauge: View { - @State var batteryLevel = 64.0 + @State var batteryLevel = 0.0 private let minValue = 1.0 private let maxValue = 100.00 - let gradient = Gradient(colors: [.red, .yellow, .green]) - var body: some View { VStack { - Gauge(value: batteryLevel, in: minValue...maxValue) { - Label("Battery Level %", systemImage: "battery.0") - } currentValueLabel: { - Text(Int(batteryLevel), format: .percent) + + if batteryLevel == 0.0 { + // Plugged in + Image(systemName: "powerplug") + .font(.largeTitle) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + } else { + + Gauge(value: batteryLevel, in: minValue...maxValue) { + if batteryLevel > 1.0 && batteryLevel <= 9 { + Label("Battery Level %", systemImage: "battery.0") + } else if batteryLevel > 10.0 && batteryLevel <= 25.00 { + Label("Battery Level %", systemImage: "battery.25") + } else if batteryLevel > 26.0 && batteryLevel <= 50.00 { + Label("Battery Level %", systemImage: "battery.50") + } else if batteryLevel > 51.0 && batteryLevel <= 75.00 { + Label("Battery Level %", systemImage: "battery.50") + } else { + Label("Battery Level %", systemImage: "battery.100") + } + } currentValueLabel: { + Text(Int(batteryLevel), format: .percent) + } + .tint(.green) + .gaugeStyle(.accessoryCircular) } - .tint(gradient) } - .gaugeStyle(.accessoryCircular) - - .padding() } } diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index b773824c..1e1951b7 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -338,7 +338,6 @@ struct NodeDetail: View { .font(.callout) .foregroundColor(.gray) .fixedSize() - .offset(y: -25) } } From 446368b0bd6a2bc46cf8caa39e6f75618aaac1c2 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 28 Sep 2022 16:27:09 -0700 Subject: [PATCH 26/36] Extra battery level case --- Meshtastic/Views/Helpers/BatteryGauge.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Views/Helpers/BatteryGauge.swift b/Meshtastic/Views/Helpers/BatteryGauge.swift index 7c2448d9..ec5ebf39 100644 --- a/Meshtastic/Views/Helpers/BatteryGauge.swift +++ b/Meshtastic/Views/Helpers/BatteryGauge.swift @@ -34,9 +34,11 @@ struct BatteryGauge: View { } else if batteryLevel > 26.0 && batteryLevel <= 50.00 { Label("Battery Level %", systemImage: "battery.50") } else if batteryLevel > 51.0 && batteryLevel <= 75.00 { - Label("Battery Level %", systemImage: "battery.50") - } else { + Label("Battery Level %", systemImage: "battery.75") + } else if batteryLevel > 76.0 && batteryLevel <= 99.00 { Label("Battery Level %", systemImage: "battery.100") + } else { + Label("Battery Level %", systemImage: "battery.100.bolt") } } currentValueLabel: { Text(Int(batteryLevel), format: .percent) From 9883afe958a213a7dc5188edb649d880ba70f6a1 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 28 Sep 2022 17:09:35 -0700 Subject: [PATCH 27/36] Finish battery gauge, delete old battery icon view --- Meshtastic.xcodeproj/project.pbxproj | 4 -- Meshtastic/Views/Helpers/BatteryGauge.swift | 30 +++++++--- Meshtastic/Views/Helpers/BatteryIcon.swift | 63 --------------------- 3 files changed, 23 insertions(+), 74 deletions(-) delete mode 100644 Meshtastic/Views/Helpers/BatteryIcon.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 797531e1..be4210bf 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -60,7 +60,6 @@ DD8EBF43285058FA00426DCA /* DisplayConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8EBF42285058FA00426DCA /* DisplayConfig.swift */; }; DD8ED9C52898D51F00B3B0AB /* NetworkConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */; }; DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */; }; - DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860B26F684AF00DC5189 /* BatteryIcon.swift */; }; DD90860E26F69BAE00DC5189 /* NodeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD90860D26F69BAE00DC5189 /* NodeMap.swift */; }; DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; }; DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */; }; @@ -171,7 +170,6 @@ DD8ED9C42898D51F00B3B0AB /* NetworkConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConfig.swift; sourceTree = ""; }; DD8ED9C7289CE4B900B3B0AB /* RoutingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingError.swift; sourceTree = ""; }; DD90860A26F645B700DC5189 /* Meshtastic.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Meshtastic.entitlements; sourceTree = ""; }; - DD90860B26F684AF00DC5189 /* BatteryIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryIcon.swift; sourceTree = ""; }; DD90860D26F69BAE00DC5189 /* NodeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeMap.swift; sourceTree = ""; }; DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRoles.swift; sourceTree = ""; }; @@ -499,7 +497,6 @@ children = ( DD47E3D526F17ED900029299 /* CircleText.swift */, DD47E3D826F3093800029299 /* MessageBubble.swift */, - DD90860B26F684AF00DC5189 /* BatteryIcon.swift */, DDF924C926FBB953009FE055 /* ConnectedDevice.swift */, DDC3B273283F411B00AC321C /* LastHeardText.swift */, DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */, @@ -712,7 +709,6 @@ DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */, DD6193792863875F00E59241 /* SerialConfig.swift in Sources */, DDAF8C6926ED0D070058C060 /* deviceonly.pb.swift in Sources */, - DD90860C26F684AF00DC5189 /* BatteryIcon.swift in Sources */, DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */, DD35018B2852FC79000FC853 /* UserSettings.swift in Sources */, DD2160AF28C5552500C17253 /* MQTTConfig.swift in Sources */, diff --git a/Meshtastic/Views/Helpers/BatteryGauge.swift b/Meshtastic/Views/Helpers/BatteryGauge.swift index ec5ebf39..a68eeec0 100644 --- a/Meshtastic/Views/Helpers/BatteryGauge.swift +++ b/Meshtastic/Views/Helpers/BatteryGauge.swift @@ -2,7 +2,7 @@ // BatteryGauge.swift // Meshtastic // -// Created by Garth Vander Houwen on 9/28/22. +// Copyright(c) Garth Vander Houwen 9/28/22. // import SwiftUI @@ -26,16 +26,17 @@ struct BatteryGauge: View { .symbolRenderingMode(.hierarchical) } else { + let gradient = Gradient(colors: [.red, .orange, .green]) Gauge(value: batteryLevel, in: minValue...maxValue) { - if batteryLevel > 1.0 && batteryLevel <= 9 { + if batteryLevel > 1.0 && batteryLevel < 10 { Label("Battery Level %", systemImage: "battery.0") - } else if batteryLevel > 10.0 && batteryLevel <= 25.00 { + } else if batteryLevel >= 10.0 && batteryLevel < 25.00 { Label("Battery Level %", systemImage: "battery.25") - } else if batteryLevel > 26.0 && batteryLevel <= 50.00 { + } else if batteryLevel >= 25.0 && batteryLevel < 50.00 { Label("Battery Level %", systemImage: "battery.50") - } else if batteryLevel > 51.0 && batteryLevel <= 75.00 { + } else if batteryLevel >= 50.0 && batteryLevel < 75.00 { Label("Battery Level %", systemImage: "battery.75") - } else if batteryLevel > 76.0 && batteryLevel <= 99.00 { + } else if batteryLevel >= 75.0 && batteryLevel <= 99.00 { Label("Battery Level %", systemImage: "battery.100") } else { Label("Battery Level %", systemImage: "battery.100.bolt") @@ -43,9 +44,24 @@ struct BatteryGauge: View { } currentValueLabel: { Text(Int(batteryLevel), format: .percent) } - .tint(.green) + .tint(gradient) .gaugeStyle(.accessoryCircular) } } } } + +struct BatteryGauge_Previews: PreviewProvider { + static var previews: some View { + + VStack { + BatteryGauge(batteryLevel: 0.0) + BatteryGauge(batteryLevel: 9.0) + BatteryGauge(batteryLevel: 24.0) + BatteryGauge(batteryLevel: 49.0) + BatteryGauge(batteryLevel: 74.0) + BatteryGauge(batteryLevel: 99.0) + BatteryGauge(batteryLevel: 100.0) + } + } +} diff --git a/Meshtastic/Views/Helpers/BatteryIcon.swift b/Meshtastic/Views/Helpers/BatteryIcon.swift deleted file mode 100644 index a67e580d..00000000 --- a/Meshtastic/Views/Helpers/BatteryIcon.swift +++ /dev/null @@ -1,63 +0,0 @@ -import SwiftUI - -struct BatteryIcon: View { - var batteryLevel: Int32? - var font: Font - var color: Color - - var body: some View { - - if batteryLevel == 100 { - - Image(systemName: "battery.100.bolt") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel! < 100 && batteryLevel! > 74 { - - Image(systemName: "battery.75") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel! < 75 && batteryLevel! > 49 { - - Image(systemName: "battery.50") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel! < 50 && batteryLevel! > 14 { - - Image(systemName: "battery.25") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else if batteryLevel! == 0 { - - Image(systemName: "powerplug") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } else { - - Image(systemName: "battery.0") - .font(font) - .foregroundColor(color) - .symbolRenderingMode(.hierarchical) - } - } -} - -struct BatteryIcon_Previews: PreviewProvider { - static var previews: some View { - BatteryIcon(batteryLevel: 100, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - BatteryIcon(batteryLevel: 99, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - BatteryIcon(batteryLevel: 74, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - BatteryIcon(batteryLevel: 49, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - BatteryIcon(batteryLevel: 14, font: .title2, color: Color.accentColor) - .previewLayout(.fixed(width: 75, height: 75)) - } -} From f03d0d8200b1d5c2c6103534441248506e126acb Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Wed, 28 Sep 2022 20:10:10 -0700 Subject: [PATCH 28/36] Delete extra preferred peripheral user setting, add some previews --- Meshtastic/Model/UserSettings.swift | 6 ------ Meshtastic/Views/Bluetooth/Connect.swift | 13 ++++------- Meshtastic/Views/Helpers/DistanceText.swift | 14 +++++++++++- Meshtastic/Views/Settings/AppSettings.swift | 24 ++++++++++----------- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/Meshtastic/Model/UserSettings.swift b/Meshtastic/Model/UserSettings.swift index c1ef080d..9c0c2e9f 100644 --- a/Meshtastic/Model/UserSettings.swift +++ b/Meshtastic/Model/UserSettings.swift @@ -13,11 +13,6 @@ class UserSettings: ObservableObject { UserDefaults.standard.set(meshtasticUsername, forKey: "meshtasticusername") } } - @Published var preferredPeripheralName: String { - didSet { - UserDefaults.standard.set(preferredPeripheralName, forKey: "preferredPeripheralName") - } - } @Published var preferredPeripheralId: String { didSet { UserDefaults.standard.set(preferredPeripheralId, forKey: "preferredPeripheralId") @@ -52,7 +47,6 @@ class UserSettings: ObservableObject { init() { self.meshtasticUsername = UserDefaults.standard.object(forKey: "meshtasticusername") as? String ?? "" - self.preferredPeripheralName = UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? "" self.preferredPeripheralId = UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false self.provideLocationInterval = UserDefaults.standard.object(forKey: "provideLocationInterval") as? Int ?? 900 diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 4cf30148..cd655482 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -94,23 +94,18 @@ struct Connect: View { if bleManager.connectedPeripheral != nil { - let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "") - userSettings.preferredPeripheralName = deviceName - - } else { - - userSettings.preferredPeripheralName = bleManager.connectedPeripheral.longName + + userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString + bleManager.preferredPeripheral = true + } - userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString - bleManager.preferredPeripheral = true } else { if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId { userSettings.preferredPeripheralId = "" - userSettings.preferredPeripheralName = "" bleManager.preferredPeripheral = false } } diff --git a/Meshtastic/Views/Helpers/DistanceText.swift b/Meshtastic/Views/Helpers/DistanceText.swift index 1709b5f4..59d58bb8 100644 --- a/Meshtastic/Views/Helpers/DistanceText.swift +++ b/Meshtastic/Views/Helpers/DistanceText.swift @@ -16,7 +16,19 @@ struct DistanceText: View { var body: some View { let distanceFormatter = MKDistanceFormatter() - Text("Distance: \(distanceFormatter.string(fromDistance: Double(meters)))") } } +struct DistanceText_Previews: PreviewProvider { + static var previews: some View { + + VStack { + + DistanceText(meters: 100) + DistanceText(meters: 1000) + DistanceText(meters: 10000) + DistanceText(meters: 100000) + DistanceText(meters: 1000000) + } + } +} diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 5bce4f0b..c56fbe46 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -105,18 +105,18 @@ struct AppSettings: View { .disableAutocorrection(true) .listRowSeparator(.visible) - HStack { - Label("Radio", systemImage: "flipphone") - Text(userSettings.preferredPeripheralName) - .foregroundColor(.gray) - - } - Text("This option is set via the preferred radio toggle for the connected device on the bluetooth tab.") - .font(.caption) - .listRowSeparator(.hidden) - Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range.") - .font(.caption2) - .foregroundColor(.gray) +// HStack { +// Label("Radio", systemImage: "flipphone") +// Text(userSettings.preferredPeripheralName) +// .foregroundColor(.gray) +// +// } +// Text("This option is set via the preferred radio toggle for the connected device on the bluetooth tab.") +// .font(.caption) +// .listRowSeparator(.hidden) +// Text("The preferred radio will automatically reconnect if it becomes disconnected and is still within range.") +// .font(.caption2) +// .foregroundColor(.gray) } Section(header: Text("Options")) { From 3dbf81494c964af085bcbf795c3a5b85a62a062e Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 29 Sep 2022 07:17:07 -0700 Subject: [PATCH 29/36] Channel Settings updates --- Meshtastic/Views/Settings/ShareChannels.swift | 124 +++++++++++++----- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 3fda36af..76be78b2 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -35,6 +35,8 @@ struct ShareChannels: View { @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings + @State var channel1Enabled = true + var node: NodeInfoEntity? @State private var text = "https://meshtastic.org/E/#test" @@ -50,10 +52,63 @@ struct ShareChannels: View { ScrollView { + Text("The current LoRa configuration will also be shared.") + .fixedSize(horizontal: false, vertical: true) + .font(.callout) + .padding(.bottom) + + VStack { + if node != nil { + + Grid(alignment: .top, horizontalSpacing: 2) { + + GridRow { + Spacer() + Text("Include") + .font(.caption) + .fontWeight(.bold) + Text("Name") + .font(.caption) + .fontWeight(.bold) + Text("Role") + .font(.caption) + .fontWeight(.bold) + Spacer() + } + Divider() + + ForEach(node!.myInfo!.channels?.array.sorted(by: { ($0 as! ChannelEntity).index < ($1 as! ChannelEntity).index }) as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in + + GridRow { + Spacer() + Toggle("Channel 1 Enabled", isOn: $channel1Enabled) + .toggleStyle(.switch) + .labelsHidden() + + Text("Channel - \(channel.index)") + Spacer() + } + } + } + } + } + + VStack { + + Divider() + ShareLink( + item: text, + preview: SharePreview( + "Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", + image: Image(systemName: "qrcode") + ) + ) + .presentationDetents([.large, .large]) + .font(.title3) + Divider() + } + VStack { - Text("Scan the QR code below with the Apple or Android device you would like to share your channel settings with.") - .fixedSize(horizontal: false, vertical: true) - .font(.callout) let image = qrCodeImage.generateQRCode(from: text) Image(uiImage: image) @@ -67,46 +122,43 @@ struct ShareChannels: View { alignment: .center ) - VStack { - ShareLink( - item: text, - preview: SharePreview( - "Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", - image: Image(systemName: "qrcode") - ) - ) - .presentationDetents([.large, .large]) - .font(.title3) - } + Divider() + - if node != nil && node!.loRaConfig != nil { - - HStack { - - let preset = ModemPresets(rawValue: Int(node!.loRaConfig!.modemPreset)) - Text("Modem Preset \(preset!.description)").font(.title3) - } - } VStack { - Text("Number of Channels: \(node?.myInfo?.maxChannels ?? 0)").font(.title2) - - if node != nil { - - ForEach(node!.myInfo!.channels?.array.sorted(by: { ($0 as! ChannelEntity).index < ($1 as! ChannelEntity).index }) as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in - - VStack { - - - Text("Channel: \(channel.index) Name: \(channel.name ?? "")") - } - } - } +// if node != nil { +// +// ForEach(node!.myInfo!.channels?.array.sorted(by: { ($0 as! ChannelEntity).index < ($1 as! ChannelEntity).index }) as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in +// +// VStack{ +// +// Grid{ +// +// GridRow { +// Text("Include") +// Image(systemName: "globe") +// } +// GridRow { +// Toggle("Channel 1 Enabled", isOn: $channel1Enabled) +// .toggleStyle(.switch) +// .labelsHidden() +// Text("World") +// } +// } +// } +// HStack { +// +// +// Text("Channel: \(channel.index) Name: \(channel.name ?? "EMPTY") Role: \(channel.role)") +// } +// } +// } } .frame(width: bounds.size.width, height: bounds.size.height) } } - .navigationTitle("Share Channel") + .navigationTitle("Share Channels") .navigationBarTitleDisplayMode(.automatic) .navigationBarItems(trailing: From cbf7d053f70a802763727a52055d9f5b204fb0d7 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Thu, 29 Sep 2022 22:24:05 -0700 Subject: [PATCH 30/36] Share channels mockup --- Meshtastic/Helpers/LocationHelper.swift | 3 +- Meshtastic/Views/Settings/ShareChannels.swift | 127 ++++++++++-------- 2 files changed, 71 insertions(+), 59 deletions(-) diff --git a/Meshtastic/Helpers/LocationHelper.swift b/Meshtastic/Helpers/LocationHelper.swift index 1137860a..547b0f62 100644 --- a/Meshtastic/Helpers/LocationHelper.swift +++ b/Meshtastic/Helpers/LocationHelper.swift @@ -10,6 +10,7 @@ class LocationHelper: NSObject, ObservableObject { static let DefaultAltitude = CLLocationDistance(integerLiteral: 0) static let DefaultSpeed = CLLocationSpeed(integerLiteral: 0) static let DefaultHeading = CLLocationDirection(integerLiteral: 0) + static let DefaultTime = Date.init(timeIntervalSince1970: 0) static var currentLocation: CLLocationCoordinate2D { @@ -46,7 +47,7 @@ class LocationHelper: NSObject, ObservableObject { static var currentTimestamp: Date { guard let timestamp = shared.locationManager.location?.timestamp else { - return Date.now + return DefaultTime } return timestamp } diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 76be78b2..6ce8dae1 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -35,7 +35,14 @@ struct ShareChannels: View { @EnvironmentObject var bleManager: BLEManager @EnvironmentObject var userSettings: UserSettings - @State var channel1Enabled = true + @State var includeChannel0 = true + @State var includeChannel1 = true + @State var includeChannel2 = true + @State var includeChannel3 = false + @State var includeChannel4 = false + @State var includeChannel5 = false + @State var includeChannel6 = false + @State var includeChannel7 = true var node: NodeInfoEntity? @@ -52,11 +59,6 @@ struct ShareChannels: View { ScrollView { - Text("The current LoRa configuration will also be shared.") - .fixedSize(horizontal: false, vertical: true) - .font(.callout) - .padding(.bottom) - VStack { if node != nil { @@ -70,9 +72,6 @@ struct ShareChannels: View { Text("Name") .font(.caption) .fontWeight(.bold) - Text("Role") - .font(.caption) - .fontWeight(.bold) Spacer() } Divider() @@ -81,81 +80,93 @@ struct ShareChannels: View { GridRow { Spacer() - Toggle("Channel 1 Enabled", isOn: $channel1Enabled) - .toggleStyle(.switch) - .labelsHidden() - - Text("Channel - \(channel.index)") + if channel.index == 0 { + Toggle("Channel 0 Included", isOn: $includeChannel0) + .toggleStyle(.switch) + .labelsHidden() + .disabled(true) + Text("Primary Channel") + + } else if channel.index == 1 { + Toggle("Channel 1 Included", isOn: $includeChannel1) + .toggleStyle(.switch) + .labelsHidden() + Text("Public Channel") + } else if channel.index == 2 { + Toggle("Channel 2 Included", isOn: $includeChannel2) + .toggleStyle(.switch) + .labelsHidden() + } else if channel.index == 3 { + Toggle("Channel 3 Included", isOn: $includeChannel3) + .toggleStyle(.switch) + .labelsHidden() + } else if channel.index == 4 { + Toggle("Channel 4 Included", isOn: $includeChannel4) + .toggleStyle(.switch) + .labelsHidden() + .disabled(true) + } else if channel.index == 5 { + Toggle("Channel 5 Included", isOn: $includeChannel5) + .toggleStyle(.switch) + .labelsHidden() + .disabled(true) + } else if channel.index == 6 { + Toggle("Channel 6 Included", isOn: $includeChannel6) + .toggleStyle(.switch) + .labelsHidden() + .disabled(true) + } else if channel.index == 7 { + Toggle("Channel 7 Included", isOn: $includeChannel7) + .toggleStyle(.switch) + .labelsHidden() + Text("Admin Channel") + } + if channel.index > 1 && channel.index < 4{ + Text("Private Chat - \(channel.index)") + } + if channel.index > 3 && channel.index < 7{ + Text("Channel - \(channel.index)") + } Spacer() } } } } } + let image = qrCodeImage.generateQRCode(from: text) VStack { Divider() - ShareLink( - item: text, - preview: SharePreview( - "Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", - image: Image(systemName: "qrcode") - ) + + ShareLink("Share QR Code & Link", + item: Image(uiImage: image), + subject: Text("Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")"), + message: Text("Open the link or scan the QR code on Android, iOS, iPadOS or macOS with the Meshtastic app and you will be prompted to save these channel settings to your device: \(text)"), + preview: SharePreview("Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", + image: Image(uiImage: image)) ) .presentationDetents([.large, .large]) .font(.title3) - Divider() - } - - VStack { - let image = qrCodeImage.generateQRCode(from: text) + Divider() + Image(uiImage: image) .resizable() .scaledToFit() .frame( - minWidth: smallest * 0.8, - maxWidth: smallest * 0.8, - minHeight: smallest * 0.8, - maxHeight: smallest * 0.8, + minWidth: smallest * 0.75, + maxWidth: smallest * 0.75, + minHeight: smallest * 0.75, + maxHeight: smallest * 0.75, alignment: .center ) Divider() - VStack { -// if node != nil { -// -// ForEach(node!.myInfo!.channels?.array.sorted(by: { ($0 as! ChannelEntity).index < ($1 as! ChannelEntity).index }) as! [ChannelEntity], id: \.self) { (channel: ChannelEntity) in -// -// VStack{ -// -// Grid{ -// -// GridRow { -// Text("Include") -// Image(systemName: "globe") -// } -// GridRow { -// Toggle("Channel 1 Enabled", isOn: $channel1Enabled) -// .toggleStyle(.switch) -// .labelsHidden() -// Text("World") -// } -// } -// } -// HStack { -// -// -// Text("Channel: \(channel.index) Name: \(channel.name ?? "EMPTY") Role: \(channel.role)") -// } -// } -// } } - .frame(width: bounds.size.width, height: bounds.size.height) } } .navigationTitle("Share Channels") From fb811a0a841cf24fc74ea1aaed8d870102b1f652 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Sep 2022 17:10:03 -0700 Subject: [PATCH 31/36] Assorted bug fixes and cleanup, environmental export fix, line limit of 500 for mesh log --- Meshtastic.xcodeproj/project.pbxproj | 4 ++ Meshtastic/Enums/MessagingEnums.swift | 11 +++++ Meshtastic/Export/WriteCsvFile.swift | 4 +- Meshtastic/Views/Bluetooth/Connect.swift | 20 ++++----- .../Views/Nodes/EnvironmentMetricsLog.swift | 3 -- .../Settings/Config/PositionConfig.swift | 20 ++++----- Meshtastic/Views/Settings/MeshLog.swift | 30 +++++++++++-- Meshtastic/Views/Settings/ShareChannels.swift | 44 ++++++++----------- 8 files changed, 82 insertions(+), 54 deletions(-) create mode 100644 Meshtastic/Enums/MessagingEnums.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index be4210bf..4d2e9cf9 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ DD3CC6B528E33FD100FA9159 /* ShareChannels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */; }; DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BA28E366DF00FA9159 /* Meshtastic.xcdatamodeld */; }; DD3CC6BE28E4CD9800FA9159 /* BatteryGauge.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */; }; + DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */; }; DD4033C228B286B70096A444 /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4033C128B286B70096A444 /* Onboarding.swift */; }; DD41582628582E9B009B0E59 /* DeviceConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD41582528582E9B009B0E59 /* DeviceConfig.swift */; }; DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD415827285859C4009B0E59 /* TelemetryConfig.swift */; }; @@ -138,6 +139,7 @@ DD3CC6B428E33FD100FA9159 /* ShareChannels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannels.swift; sourceTree = ""; }; DD3CC6BB28E366DF00FA9159 /* MeshtasticDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticDataModel.xcdatamodel; sourceTree = ""; }; DD3CC6BD28E4CD9800FA9159 /* BatteryGauge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryGauge.swift; sourceTree = ""; }; + DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagingEnums.swift; sourceTree = ""; }; DD4033C128B286B70096A444 /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = ""; }; DD41582528582E9B009B0E59 /* DeviceConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceConfig.swift; sourceTree = ""; }; DD415827285859C4009B0E59 /* TelemetryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryConfig.swift; sourceTree = ""; }; @@ -349,6 +351,7 @@ DD1925B628CDA5A400720036 /* CannedMessagesConfigEnums.swift */, DD1925B828CDA93900720036 /* SerialConfigEnums.swift */, DDA1C48D28DB49D3009933EC /* ChannelRoles.swift */, + DD3CC6BF28E7A60700FA9159 /* MessagingEnums.swift */, ); path = Enums; sourceTree = ""; @@ -764,6 +767,7 @@ DDB6ABE228B13FB500384BA1 /* PositionConfigEnums.swift in Sources */, DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */, DD73FD1128750779000852D6 /* PositionLog.swift in Sources */, + DD3CC6C028E7A60700FA9159 /* MessagingEnums.swift in Sources */, C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */, DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */, DD3CC6BC28E366DF00FA9159 /* Meshtastic.xcdatamodeld in Sources */, diff --git a/Meshtastic/Enums/MessagingEnums.swift b/Meshtastic/Enums/MessagingEnums.swift new file mode 100644 index 00000000..637cfdc6 --- /dev/null +++ b/Meshtastic/Enums/MessagingEnums.swift @@ -0,0 +1,11 @@ +// +// MessagingEnums.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 9/30/22. +// + +enum BubblePosition { + case left + case right +} diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index accb70a3..e5cd7843 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -34,14 +34,14 @@ func TelemetryToCsvFile(telemetry: [TelemetryEntity], metricsType: Int) -> Strin } } - } else { + } else if metricsType == 1 { // Create Environment Telemetry Header csvString = "Temperature, Relative Humidity, Barometric Pressure, Gas Resistance, Voltage, Current" for dm in telemetry{ - if dm.metricsType == 0 { + if dm.metricsType == 1 { csvString += "\n" csvString += String("\(dm.temperature)°") diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index cd655482..e62f3766 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -63,17 +63,10 @@ struct Connect: View { if bleManager.connectedPeripheral != nil { Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.firmwareVersion) .font(.caption).foregroundColor(Color.gray) - Text("Bitrate: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) - - - Text("Channel Utilization: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) - Text("Air Time: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00)) - .font(.caption).foregroundColor(Color.gray) } if bleManager.connectedPeripheral.subscribed { Text("Properly Subscribed").font(.caption) + .foregroundColor(.green) } else { Text("Attempting to connect. . . ").font(.caption) .foregroundColor(.orange) @@ -113,6 +106,8 @@ struct Connect: View { } } + .font(.caption).foregroundColor(Color.gray) + .padding([.top, .bottom]) .swipeActions { Button(role: .destructive) { @@ -124,8 +119,13 @@ struct Connect: View { Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash") } } - .padding([.top, .bottom]) - + .contextMenu{ + + Text("My Node Info") + Label("Bitrate \(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00))", systemImage: "pencil.circle") + Text("Ch. Utilization \(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00))") + Text("Air Time \(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00))") + } } else { HStack { diff --git a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift index 655e693e..6b17cbbc 100644 --- a/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift +++ b/Meshtastic/Views/Nodes/EnvironmentMetricsLog.swift @@ -38,9 +38,6 @@ struct EnvironmentMetricsLog: View { Text("Gas") .font(.caption) .fontWeight(.bold) - Text("Gas") - .font(.caption) - .fontWeight(.bold) Text("DC") .font(.caption) .fontWeight(.bold) diff --git a/Meshtastic/Views/Settings/Config/PositionConfig.swift b/Meshtastic/Views/Settings/Config/PositionConfig.swift index 24b2e00c..2c335e4f 100644 --- a/Meshtastic/Views/Settings/Config/PositionConfig.swift +++ b/Meshtastic/Views/Settings/Config/PositionConfig.swift @@ -13,7 +13,7 @@ struct PositionFlags: OptionSet static let Altitude = PositionFlags(rawValue: 1) static let AltitudeMsl = PositionFlags(rawValue: 2) - static let GeoSep = PositionFlags(rawValue: 4) + static let GeoidalSeparation = PositionFlags(rawValue: 4) static let Dop = PositionFlags(rawValue: 8) static let Hvdop = PositionFlags(rawValue: 16) static let Satsinview = PositionFlags(rawValue: 32) @@ -48,7 +48,7 @@ struct PositionConfig: View { /// Altitude value is MSL - 2 @State var includeAltitudeMsl = false /// Include geoidal separation - 4 - @State var includeGeoSep = false + @State var includeGeoidalSeparation = false /// Include the DOP value ; PDOP used by default, see below - 8 @State var includeDop = false /// If POS_DOP set, send separate HDOP / VDOP values instead of PDOP - 16 @@ -56,7 +56,7 @@ struct PositionConfig: View { /// Include number of "satellites in view" - 32 @State var includeSatsinview = false /// Include a sequence number incremented per packet - 64 - @State var includeSeqNos = false + @State var includeSeqNo = false /// Include positional timestamp (from GPS solution) - 128 @State var includeTimestamp = false /// Include positional heading - 256 @@ -162,7 +162,7 @@ struct PositionConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) - Toggle(isOn: $includeSeqNos) { //64 + Toggle(isOn: $includeSeqNo) { //64 Label("Sequence number", systemImage: "number") } @@ -188,7 +188,7 @@ struct PositionConfig: View { } Section(header: Text("Advanced Position Flags")) { - Toggle(isOn: $includeGeoSep) { + Toggle(isOn: $includeGeoidalSeparation) { Text("Geoidal Seperation") } @@ -242,11 +242,11 @@ struct PositionConfig: View { if includeAltitude { pf.insert(.Altitude) } if includeAltitudeMsl { pf.insert(.AltitudeMsl) } - if includeGeoSep { pf.insert(.GeoSep) } + if includeGeoidalSeparation { pf.insert(.GeoidalSeparation) } if includeDop { pf.insert(.Dop) } if includeHvdop { pf.insert(.Hvdop) } if includeSatsinview { pf.insert(.Satsinview) } - if includeSeqNos { pf.insert(.SeqNos) } + if includeSeqNo { pf.insert(.SeqNos) } if includeTimestamp { pf.insert(.Timestamp) } if includeSpeed { pf.insert(.Speed) } if includeHeading { pf.insert(.Heading) } @@ -296,11 +296,11 @@ struct PositionConfig: View { if pf.contains(.Altitude) { self.includeAltitude = true } else { self.includeAltitude = false } if pf.contains(.AltitudeMsl) { self.includeAltitudeMsl = true } else { self.includeAltitudeMsl = false } - if pf.contains(.GeoSep) { self.includeGeoSep = true } else { self.includeGeoSep = false } + if pf.contains(.GeoidalSeparation) { self.includeGeoidalSeparation = true } else { self.includeGeoidalSeparation = false } if pf.contains(.Dop) { self.includeDop = true } else { self.includeDop = false } if pf.contains(.Hvdop) { self.includeHvdop = true } else { self.includeHvdop = false } if pf.contains(.Satsinview) { self.includeSatsinview = true } else { self.includeSatsinview = false } - if pf.contains(.SeqNos) { self.includeSeqNos = true } else { self.includeSeqNos = false } + if pf.contains(.SeqNos) { self.includeSeqNo = true } else { self.includeSeqNo = false } if pf.contains(.Timestamp) { self.includeTimestamp = true } else { self.includeTimestamp = false } if pf.contains(.Speed) { self.includeSpeed = true } else { self.includeSpeed = false } if pf.contains(.Heading) { self.includeHeading = true } else { self.includeHeading = false } @@ -352,7 +352,7 @@ struct PositionConfig: View { if newPositionBroadcastSeconds != node!.positionConfig!.positionBroadcastSeconds { hasChanges = true } } } - .onChange(of: includeAltitude || includeAltitudeMsl || includeGeoSep || includeDop || includeHvdop || includeSatsinview || includeSeqNos || includeTimestamp || includeSpeed || includeHeading) { newFlags in + .onChange(of: includeAltitude || includeAltitudeMsl || includeGeoidalSeparation || includeDop || includeHvdop || includeSatsinview || includeSeqNo || includeTimestamp || includeSpeed || includeHeading) { newFlags in // hasChanges = true } .navigationViewStyle(StackNavigationViewStyle()) diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index 1728f5ee..b9cfd355 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -17,11 +17,33 @@ struct MeshLog: View { let url = logFile! logs.removeAll() - for try await log in url.lines { - logs.append(log) - document.logFile.append("\(log) \n") + + var lineCount = 0 + let lineLimit = 500 + + // Get the number of lines + for try await _ in url.lines { + lineCount += 1 } - logs.reverse() + + var startingLog = 0 + // Set the record to start with if we have more lines than the limit + if lineCount > lineLimit { + startingLog = lineCount - lineLimit + } + + var lineNumber = 0 + + for try await log in url.lines { + if lineNumber >= startingLog { + + logs.append(log) + document.logFile.append("\(log) \n") + } + lineNumber += 1 + } + logs.reverse() + } catch { // Stop adding logs when an error is thrown } diff --git a/Meshtastic/Views/Settings/ShareChannels.swift b/Meshtastic/Views/Settings/ShareChannels.swift index 6ce8dae1..ea89cb6d 100644 --- a/Meshtastic/Views/Settings/ShareChannels.swift +++ b/Meshtastic/Views/Settings/ShareChannels.swift @@ -46,7 +46,7 @@ struct ShareChannels: View { var node: NodeInfoEntity? - @State private var text = "https://meshtastic.org/E/#test" + @State private var channelsUrl = "https://meshtastic.org/e/#test" var qrCodeImage = QrCodeImage() var body: some View { @@ -133,47 +133,41 @@ struct ShareChannels: View { } } } - let image = qrCodeImage.generateQRCode(from: text) + let qrImage = qrCodeImage.generateQRCode(from: channelsUrl) VStack { Divider() - + ShareLink("Share QR Code & Link", - item: Image(uiImage: image), - subject: Text("Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")"), - message: Text("Open the link or scan the QR code on Android, iOS, iPadOS or macOS with the Meshtastic app and you will be prompted to save these channel settings to your device: \(text)"), - preview: SharePreview("Meshtastic Channel Settings From Node \(node?.user?.shortName ?? "????")", - image: Image(uiImage: image)) + item: Image(uiImage: qrImage), + subject: Text("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you"), + message: Text(channelsUrl), + preview: SharePreview("Meshtastic Node \(node?.user?.shortName ?? "????") has shared channels with you", + image: Image(uiImage: qrImage)) ) .presentationDetents([.large, .large]) - .font(.title3) - - Divider() - Image(uiImage: image) + Divider() + + Image(uiImage: qrImage) .resizable() .scaledToFit() .frame( - minWidth: smallest * 0.75, - maxWidth: smallest * 0.75, - minHeight: smallest * 0.75, - maxHeight: smallest * 0.75, - alignment: .center + minWidth: smallest * 0.7, + maxWidth: smallest * 0.7, + minHeight: smallest * 0.7, + maxHeight: smallest * 0.7, + alignment: .top ) - Divider() - - VStack { - - } } } - .navigationTitle("Share Channels") - .navigationBarTitleDisplayMode(.automatic) + .navigationTitle("Generate QR Code") + .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: - ZStack { + ZStack { ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????") }) From 6ae79b6ad69c26d796a25eca6b6b882b48f9a397 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Sep 2022 20:25:41 -0700 Subject: [PATCH 32/36] Add position flags to position log --- Meshtastic/Export/WriteCsvFile.swift | 11 +++++- .../MeshtasticDataModel.xcdatamodel/contents | 3 ++ Meshtastic/Views/Nodes/PositionLog.swift | 36 ++++++++++++------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index e5cd7843..d6b2a5cf 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -69,7 +69,7 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { var csvString: String = "" // Create Position Header - csvString = "Latitude, Longitude, Altitude, Timestamp" + csvString = "Latitude, Longitude, Alt, Sats, Speed, Heading, SeqNo, Timestamp" for pos in positions { @@ -80,6 +80,15 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { csvString += ", " csvString += String(pos.altitude) csvString += ", " + csvString += String(pos.satsInView) + csvString += ", " + csvString += String(pos.speed) + csvString += ", " + csvString += String(pos.heading) + csvString += ", " + csvString += String(pos.seqNo) + csvString += ", " + csvString += pos.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? "Unknown Age" } diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents index a7c26afb..b59226c5 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel.xcdatamodel/contents @@ -169,9 +169,12 @@ + + + diff --git a/Meshtastic/Views/Nodes/PositionLog.swift b/Meshtastic/Views/Nodes/PositionLog.swift index e5e6d54c..9602514d 100644 --- a/Meshtastic/Views/Nodes/PositionLog.swift +++ b/Meshtastic/Views/Nodes/PositionLog.swift @@ -30,30 +30,40 @@ struct PositionLog: View { GridRow { - Text("Latitude") - .font(.caption) + Text("Lat / Long") + .font(.caption2) .fontWeight(.bold) - Text("Longitude") - .font(.caption) + Text("Sat") + .font(.caption2) .fontWeight(.bold) - Text("Alt.") - .font(.caption) + Text("Alt") + .font(.caption2) + .fontWeight(.bold) + Text("Spd") + .font(.caption2) + .fontWeight(.bold) + Text("Hd") + .font(.caption2) .fontWeight(.bold) Text("Timestamp") - .font(.caption) + .font(.caption2) .fontWeight(.bold) } Divider() ForEach(node.positions!.reversed() as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in GridRow { - Text(String(mappin.latitude ?? 0)) - .font(.caption) - Text(String(mappin.longitude ?? 0)) - .font(.caption) + Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))") + .font(.caption2) + Text(String(mappin.satsInView)) + .font(.caption2) Text(String(mappin.altitude)) - .font(.caption) + .font(.caption2) + Text(String(mappin.speed)) + .font(.caption2) + Text(String(mappin.heading)) + .font(.caption2) Text(mappin.time?.formattedDate(format: "MM/dd/yy hh:mm") ?? "Unknown time") - .font(.caption) + .font(.caption2) } } } From 68587bdba35a8069856dd866788293a06c3ef9fb Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 30 Sep 2022 21:06:52 -0700 Subject: [PATCH 33/36] Bluetooth connect page connected radio context menu --- Meshtastic/Views/Bluetooth/Connect.swift | 13 +++++++++---- Meshtastic/Views/Settings/MeshLog.swift | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index e62f3766..a6b5d53f 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -121,10 +121,15 @@ struct Connect: View { } .contextMenu{ - Text("My Node Info") - Label("Bitrate \(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00))", systemImage: "pencil.circle") - Text("Ch. Utilization \(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00))") - Text("Air Time \(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00))") + Text("Num: \(String(bleManager.connectedPeripheral.num))") + Text("Short Name: \(bleManager.connectedPeripheral.shortName)") + Text("Long Name: \(bleManager.connectedPeripheral.longName)") + Text("Unique Code: \(bleManager.connectedPeripheral.lastFourCode)") + Text("Max Channels: \(String(bleManager.connectedPeripheral.maxChannels))") + Text("Bitrate: \(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00))") + Text("Ch. Utilization: \(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00))") + Text("Air Time: \(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00))") + Text("RSSI: \(bleManager.connectedPeripheral.rssi)") } } else { diff --git a/Meshtastic/Views/Settings/MeshLog.swift b/Meshtastic/Views/Settings/MeshLog.swift index b9cfd355..a2db3e19 100644 --- a/Meshtastic/Views/Settings/MeshLog.swift +++ b/Meshtastic/Views/Settings/MeshLog.swift @@ -26,8 +26,8 @@ struct MeshLog: View { lineCount += 1 } - var startingLog = 0 // Set the record to start with if we have more lines than the limit + var startingLog = 0 if lineCount > lineLimit { startingLog = lineCount - lineLimit } From ada2093e730c9aa3a825b13144ce6d657e82cc28 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sat, 1 Oct 2022 09:37:10 -0700 Subject: [PATCH 34/36] Set preferred radio automatically --- Meshtastic/Helpers/BLEManager.swift | 13 +++++++++++-- Meshtastic/Views/Bluetooth/Connect.swift | 8 +++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 1e378eca..c8d6206a 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -252,6 +252,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.isConnected = true + if userSettings?.preferredPeripheralId.count ?? 0 < 1 { + self.userSettings?.preferredPeripheralId = peripheral.identifier.uuidString + self.preferredPeripheral = true + } else if userSettings!.preferredPeripheralId == peripheral.identifier.uuidString { + self.preferredPeripheral = true + } else { + print("Trying to connect a non prefered peripheral") + } + // Invalidate and reset connection timer count, remove any connection errors self.lastConnectionError = "" self.timeoutTimerCount = 0 @@ -259,7 +268,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph self.timeoutTimer!.invalidate() } - + // Map the peripheral to the connectedPeripheral ObservedObjects connectedPeripheral = peripherals.filter({ $0.peripheral.identifier == peripheral.identifier }).first @@ -731,7 +740,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph // Get all the channels var i: UInt32 = 1; - var max: Int32 = self.connectedPeripheral.maxChannels + let max: Int32 = self.connectedPeripheral.maxChannels Timer.scheduledTimer(withTimeInterval: 0.4, repeats: true) { timer in diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index a6b5d53f..1a554947 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -79,17 +79,18 @@ struct Connect: View { Text("Preferred").font(.caption2) Text("Radio").font(.caption2) - Toggle("Preferred Radio", isOn: $isPreferredRadio) + Toggle("Preferred Radio", isOn: $bleManager.preferredPeripheral) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) .labelsHidden() - .onChange(of: isPreferredRadio) { value in + .onChange(of: bleManager.preferredPeripheral) { value in if value { if bleManager.connectedPeripheral != nil { userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString - bleManager.preferredPeripheral = true + bleManager.preferredPeripheral = true + isPreferredRadio = true } @@ -100,6 +101,7 @@ struct Connect: View { userSettings.preferredPeripheralId = "" bleManager.preferredPeripheral = false + isPreferredRadio = false } } } From fdb0f6310e094df7f9250a8883dc22d42c62609f Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 2 Oct 2022 09:19:03 -0700 Subject: [PATCH 35/36] Add reset nodedb to device config --- Meshtastic/Helpers/BLEManager.swift | 50 +++++++ Meshtastic/Views/Nodes/NodeDetail.swift | 122 +++++++++--------- .../Views/Settings/Config/DeviceConfig.swift | 71 ++++++---- 3 files changed, 160 insertions(+), 83 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index c8d6206a..425a9822 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1155,6 +1155,56 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return false } + public func sendNodeDBReset(destNum: Int64) -> Bool { + + var adminPacket = AdminMessage() + adminPacket.nodedbReset = 1 + + var meshPacket: MeshPacket = MeshPacket() + meshPacket.to = 0//UInt32(connectedPeripheral.num) + meshPacket.from = 0 //UInt32(connectedPeripheral.num) + meshPacket.id = UInt32.random(in: UInt32(UInt8.max).. Int64 { var adminPacket = AdminMessage() diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 1e1951b7..43d9eb7e 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -79,67 +79,6 @@ struct NodeDetail: View { } ScrollView { - - if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { - - HStack { - - if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { - - Button(action: { - - showingShutdownConfirm = true - }) { - - Label("Power Off", systemImage: "power") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "Are you sure?", - isPresented: $showingShutdownConfirm - ) { - Button("Shutdown Node?", role: .destructive) { - - if !bleManager.sendShutdown(destNum: node.num) { - - print("Shutdown Failed") - } - } - } - } - - Button(action: { - - showingRebootConfirm = true - - }) { - - Label("Reboot", systemImage: "arrow.triangle.2.circlepath") - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - - "Are you sure?", - isPresented: $showingRebootConfirm - ) { - - Button("Reboot Node?", role: .destructive) { - - if !bleManager.sendReboot(destNum: node.num) { - - print("Reboot Failed") - } - } - } - } - .padding(5) - } Divider() @@ -432,6 +371,67 @@ struct NodeDetail: View { Divider() } } + + if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { + + HStack { + + if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") { + + Button(action: { + + showingShutdownConfirm = true + }) { + + Label("Power Off", systemImage: "power") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $showingShutdownConfirm + ) { + Button("Shutdown Node?", role: .destructive) { + + if !bleManager.sendShutdown(destNum: node.num) { + + print("Shutdown Failed") + } + } + } + } + + Button(action: { + + showingRebootConfirm = true + + }) { + + Label("Reboot", systemImage: "arrow.triangle.2.circlepath") + } + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + + "Are you sure?", + isPresented: $showingRebootConfirm + ) { + + Button("Reboot Node?", role: .destructive) { + + if !bleManager.sendReboot(destNum: node.num) { + + print("Reboot Failed") + } + } + } + } + .padding(5) + } } .offset( y:-40) .padding(.bottom, -40) diff --git a/Meshtastic/Views/Settings/Config/DeviceConfig.swift b/Meshtastic/Views/Settings/Config/DeviceConfig.swift index 2ac06bac..86911931 100644 --- a/Meshtastic/Views/Settings/Config/DeviceConfig.swift +++ b/Meshtastic/Views/Settings/Config/DeviceConfig.swift @@ -54,9 +54,58 @@ struct DeviceConfig: View { } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } + } .disabled(bleManager.connectedPeripheral == nil) + HStack { + + Button("Reset NodeDB", role: .destructive) { + + isPresentingFactoryResetConfirm = true + } + .disabled(bleManager.connectedPeripheral == nil) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingFactoryResetConfirm, + titleVisibility: .visible + ) { + Button("Erase the NodeDB from node and app?", role: .destructive) { + + if !bleManager.sendNodeDBReset(destNum: bleManager.connectedPeripheral.num) { + + print("NodeDB Reset Failed") + } + } + } + Button("Factory Reset", role: .destructive) { + + isPresentingFactoryResetConfirm = true + } + .disabled(bleManager.connectedPeripheral == nil) + .buttonStyle(.bordered) + .buttonBorderShape(.capsule) + .controlSize(.large) + .padding() + .confirmationDialog( + "Are you sure?", + isPresented: $isPresentingFactoryResetConfirm, + titleVisibility: .visible + ) { + Button("Erase all device settings?", role: .destructive) { + + if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num) { + + print("Factory Reset Failed") + } + } + } + } + HStack { Button { @@ -102,28 +151,6 @@ struct DeviceConfig: View { Text("After device config saves the node will reboot.") } - - Button("Factory Reset", role: .destructive) { - - isPresentingFactoryResetConfirm = true - } - .disabled(bleManager.connectedPeripheral == nil) - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - .padding() - .confirmationDialog( - "Are you sure?", - isPresented: $isPresentingFactoryResetConfirm - ) { - Button("Erase all device settings?", role: .destructive) { - - if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num) { - - print("Factory Reset Failed") - } - } - } } Spacer() } From bbb0434a3ebc8b5f1df2e6e98da2fc213cf96988 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Sun, 2 Oct 2022 10:36:14 -0700 Subject: [PATCH 36/36] Make sure position flags are saving properly to the core data database --- Meshtastic/Export/WriteCsvFile.swift | 6 +++--- Meshtastic/Helpers/MeshPackets.swift | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Export/WriteCsvFile.swift b/Meshtastic/Export/WriteCsvFile.swift index d6b2a5cf..e6a8fa66 100644 --- a/Meshtastic/Export/WriteCsvFile.swift +++ b/Meshtastic/Export/WriteCsvFile.swift @@ -69,11 +69,13 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { var csvString: String = "" // Create Position Header - csvString = "Latitude, Longitude, Alt, Sats, Speed, Heading, SeqNo, Timestamp" + csvString = "SeqNo, Latitude, Longitude, Alt, Sats, Speed, Heading, Timestamp" for pos in positions { csvString += "\n" + csvString += String(pos.seqNo) + csvString += ", " csvString += String(pos.latitude ?? 0) csvString += ", " csvString += String(pos.longitude ?? 0) @@ -86,8 +88,6 @@ func PositionToCsvFile(positions: [PositionEntity]) -> String { csvString += ", " csvString += String(pos.heading) csvString += ", " - csvString += String(pos.seqNo) - csvString += ", " csvString += pos.time?.formattedDate(format: "yyyy-MM-dd HH:mm:ss") ?? "Unknown Age" } diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index e5b40a46..cf6f6a14 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -867,10 +867,13 @@ func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedOb if nodeInfo.position.latitudeI > 0 || nodeInfo.position.longitudeI > 0 { let position = PositionEntity(context: context) + position.seqNo = Int32(nodeInfo.position.seqNumber) position.latitudeI = nodeInfo.position.latitudeI position.longitudeI = nodeInfo.position.longitudeI position.altitude = nodeInfo.position.altitude position.satsInView = Int32(nodeInfo.position.satsInView) + position.speed = Int32(nodeInfo.position.groundSpeed) + position.heading = Int32(nodeInfo.position.groundTrack) position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time))) var newPostions = [PositionEntity]() @@ -1152,11 +1155,20 @@ func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb if fetchedNode.count == 1 { let position = PositionEntity(context: context) + + position.seqNo = Int32(positionMessage.seqNumber) position.latitudeI = positionMessage.latitudeI position.longitudeI = positionMessage.longitudeI position.altitude = positionMessage.altitude position.satsInView = Int32(positionMessage.satsInView) - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + position.speed = Int32(positionMessage.groundSpeed) + position.heading = Int32(positionMessage.groundTrack) + + if positionMessage.timestamp != 0 { + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.timestamp))) + } else { + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + } let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet mutablePositions.add(position)