diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 6ea795ab..06029b9c 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -32,6 +32,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 */; }; 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 */; }; @@ -121,6 +122,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 = ""; }; DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionEntityExtension.swift; sourceTree = ""; }; DD539501276DAA6A00AD86B1 /* MapLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLocation.swift; sourceTree = ""; }; DD619373285CC7D600E59241 /* MeshtasticDataModel v 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "MeshtasticDataModel v 4.xcdatamodel"; sourceTree = ""; }; @@ -228,6 +230,7 @@ DD90860D26F69BAE00DC5189 /* NodeMap.swift */, DD2E65252767A01F00E45FC5 /* NodeDetail.swift */, DD73FD1028750779000852D6 /* LocationHistory.swift */, + DD4F23CC28779A3C001D37CB /* TelemetryLog.swift */, ); path = Nodes; sourceTree = ""; @@ -620,6 +623,7 @@ DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DD4C158C2824A91E0032668E /* module_config.pb.swift in Sources */, + DD4F23CD28779A3C001D37CB /* TelemetryLog.swift in Sources */, DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */, DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */, DDC4D568275499A500A4208E /* Persistence.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 5362d09a..4f037c29 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1259,12 +1259,13 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph } - public func saveCannedMessageModuleConfig(config: ModuleConfig.CannedMessageConfig, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 { + public func saveCannedMessageModuleConfig(config: ModuleConfig.CannedMessageConfig, messages: String, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 { var newMessageId: Int64 = 0 var adminPacket = AdminMessage() adminPacket.setModuleConfig.cannedMessage = config + //adminPacket.setCannedMessageModulePart1 = messages var meshPacket: MeshPacket = MeshPacket() meshPacket.to = UInt32(toUser.num) @@ -1316,6 +1317,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph return newMessageId } + + public func saveExternalNotificationModuleConfig(config: ModuleConfig.ExternalNotificationConfig, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 { var newMessageId: Int64 = 0 diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index 82c6addf..a54f6a12 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -1271,7 +1271,7 @@ func telemetryPacket(packet: MeshPacket, meshLogging: Bool, context: NSManagedOb if let telemetryMessage = try? Telemetry(serializedData: packet.decoded.payload) { - let telemetry = TelemetryEntity(context: context) + let telemetry = TelemetryEntity(context: context) let fetchNodeTelemetryRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) @@ -1282,24 +1282,32 @@ func telemetryPacket(packet: MeshPacket, meshLogging: Bool, context: NSManagedOb if fetchedNode.count == 1 { - // Device Metrics - telemetry.airUtilTx = telemetryMessage.deviceMetrics.airUtilTx - telemetry.channelUtilization = telemetryMessage.deviceMetrics.channelUtilization - telemetry.batteryLevel = Int32(telemetryMessage.deviceMetrics.batteryLevel) - telemetry.voltage = telemetryMessage.deviceMetrics.voltage + if telemetryMessage.variant == Telemetry.OneOf_Variant.deviceMetrics(telemetryMessage.deviceMetrics) { + + // Device Metrics + telemetry.airUtilTx = telemetryMessage.deviceMetrics.airUtilTx + telemetry.channelUtilization = telemetryMessage.deviceMetrics.channelUtilization + telemetry.batteryLevel = Int32(telemetryMessage.deviceMetrics.batteryLevel) + telemetry.voltage = telemetryMessage.deviceMetrics.voltage + telemetry.metricsType = 0 + + } else if telemetryMessage.variant == Telemetry.OneOf_Variant.environmentMetrics(telemetryMessage.environmentMetrics) { - // Environment Metrics - telemetry.barometricPressure = telemetryMessage.environmentMetrics.barometricPressure - telemetry.current = telemetryMessage.environmentMetrics.current - telemetry.gasResistance = telemetryMessage.environmentMetrics.gasResistance - telemetry.relativeHumidity = telemetryMessage.environmentMetrics.relativeHumidity - telemetry.temperature = telemetryMessage.environmentMetrics.temperature + // Environment Metrics + telemetry.barometricPressure = telemetryMessage.environmentMetrics.barometricPressure + telemetry.current = telemetryMessage.environmentMetrics.current + telemetry.gasResistance = telemetryMessage.environmentMetrics.gasResistance + telemetry.relativeHumidity = telemetryMessage.environmentMetrics.relativeHumidity + telemetry.temperature = telemetryMessage.environmentMetrics.temperature + telemetry.metricsType = 1 + + } + telemetry.time = Date(timeIntervalSince1970: TimeInterval(Int64(telemetryMessage.time))) let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as! NSMutableOrderedSet mutableTelemetries.add(telemetry) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(telemetryMessage.time))) + fetchedNode[0].lastHeard = telemetry.time fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet - fetchedNode[0].objectWillChange.send() } try context.save() diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents index 956e7d02..4945a75d 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModel v 4.xcdatamodel/contents @@ -166,6 +166,7 @@ + @@ -204,7 +205,7 @@ - + \ No newline at end of file diff --git a/Meshtastic/Views/Nodes/NodeDetail.swift b/Meshtastic/Views/Nodes/NodeDetail.swift index 1dcf72ad..7d60d4d8 100644 --- a/Meshtastic/Views/Nodes/NodeDetail.swift +++ b/Meshtastic/Views/Nodes/NodeDetail.swift @@ -69,6 +69,21 @@ struct NodeDetail: View { } + + } + } else { + + Image(node.user?.hwModel ?? "UNSET") + .resizable() + .aspectRatio(contentMode: .fit) + .cornerRadius(10) + .frame(width: bounds.size.width, height: bounds.size.height / 2) + } + + List { + + if (node.positions?.count ?? 0) > 0 { + NavigationLink { LocationHistory(node: node) } label: { @@ -80,20 +95,30 @@ struct NodeDetail: View { Text("Position History \(node.positions?.count ?? 0) Points") .font(.title2) } + .fixedSize(horizontal: false, vertical: true) } - } else { - Image(node.user?.hwModel ?? "UNSET") - .resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(10) - .frame(width: bounds.size.width, height: bounds.size.height / 2) + NavigationLink { + TelemetryLog(node: node) + } label: { + + Image(systemName: "chart.xyaxis.line") + .symbolRenderingMode(.hierarchical) + .font(.title) + + Text("Telemetry Log \(node.telemetries?.count ?? 0) Readings") + .font(.title2) + } + .fixedSize(horizontal: false, vertical: true) + } - - + .frame(height: 160) + ScrollView { - + + + if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num { HStack { diff --git a/Meshtastic/Views/Nodes/TelemetryLog.swift b/Meshtastic/Views/Nodes/TelemetryLog.swift new file mode 100644 index 00000000..cd6c7d0e --- /dev/null +++ b/Meshtastic/Views/Nodes/TelemetryLog.swift @@ -0,0 +1,134 @@ +// +// TelemetryLog.swift +// Meshtastic +// +// Copyright(c) Garth Vander Houwen 7/7/22. +// +import SwiftUI + +struct TelemetryLog: View { + + @Environment(\.managedObjectContext) var context + @EnvironmentObject var bleManager: BLEManager + + var node: NodeInfoEntity + + var body: some View { + + VStack { + + List { + + + ForEach(node.telemetries!.array as! [TelemetryEntity], id: \.self) { (tel: TelemetryEntity) in + + VStack { + + if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac { + + if tel.metricsType == 0 { + + HStack { + + Text("Device Metrics") + .font(.title3) + 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 { + + HStack { + +// Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline) +// Text("Lat/Long:").font(.caption) +// Text("\(String(tel.batteryLevel ?? 0))") +// .foregroundColor(.gray) +// .font(.callout) + + Image(systemName: "arrow.up.arrow.down.circle") + .font(.subheadline) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Alt:") + .font(.caption) + + Text("\(String(tel.voltage))m") + .foregroundColor(.gray) + .font(.callout) + } + + HStack { + + Image(systemName: "clock.badge.checkmark.fill") + .font(.subheadline) + .foregroundColor(.accentColor) + .symbolRenderingMode(.hierarchical) + Text("Time:") + .font(.caption) + DateTimeText(dateTime: tel.time) + .foregroundColor(.gray) + .font(.caption) + + } + } + } + } + } + } + .padding() + .navigationTitle("Telemetry Log \(node.telemetries?.count ?? 0) Readings") + .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 + } + } +} diff --git a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift index 834101c3..a988bb46 100644 --- a/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift +++ b/Meshtastic/Views/Settings/Config/Module/CannedMessagesConfig.swift @@ -127,6 +127,8 @@ struct CannedMessagesConfig: View { /// Generate input event on Press of this kind. @State var inputbrokerEventPress = 0 + @State var messagesPart1 = "" + var body: some View { VStack { @@ -157,6 +159,10 @@ struct CannedMessagesConfig: View { .padding(.bottom, 10) } + Section(header: Text("Messages")) { + + TextEditor(text: $messagesPart1) + } Section(header: Text("Control Type")) { @@ -310,7 +316,7 @@ struct CannedMessagesConfig: View { cmc.inputbrokerEventCcw = InputEventChars(rawValue: inputbrokerEventCcw)!.protoEnumValue() cmc.inputbrokerEventPress = InputEventChars(rawValue: inputbrokerEventPress)!.protoEnumValue() - let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true) + let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, messages: "Where are you garth?, Hello",fromUser: node!.user!, toUser: node!.user!, wantResponse: true) if adminMessageId > 0 { // Should show a saved successfully alert once I know that to be true @@ -363,12 +369,23 @@ struct CannedMessagesConfig: View { } .onChange(of: enabled) { newEnabled in - if newEnabled != node!.cannedMessageConfig!.enabled { hasChanges = true } + if node!.cannedMessageConfig != nil { + + if newEnabled != node!.cannedMessageConfig!.enabled { hasChanges = true } + } } .onChange(of: sendBell) { newBell in if newBell != node!.cannedMessageConfig!.sendBell { hasChanges = true } } + .onChange(of: rotary1Enabled) { newRot1 in + + if newRot1 != node!.cannedMessageConfig!.rotary1Enabled { hasChanges = true } + } + .onChange(of: updown1Enabled) { newUpDown in + + if newUpDown != node!.cannedMessageConfig!.updown1Enabled { hasChanges = true } + } .onChange(of: inputbrokerPinA) { newPinA in if newPinA != node!.cannedMessageConfig!.inputbrokerPinA { hasChanges = true }