From ccf5bf161059c6e843c6e71067cbe2852e709f6d Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 14 Sep 2021 21:38:12 -0700 Subject: [PATCH] Rebuild nodes page with data from device, improve layout consistency --- Meshtastic Client.xcodeproj/project.pbxproj | 51 ++- MeshtasticClient/Helpers/BLEManager.swift | 14 +- MeshtasticClient/Helpers/Extensions.swift | 47 ++- .../Helpers/HelperFunctions.swift | 11 + MeshtasticClient/Model/ModelData.swift | 2 + MeshtasticClient/Model/NodeInfoModel.swift | 37 +++ MeshtasticClient/Resources/nodeInfo.json | 71 +++-- MeshtasticClient/Resources/nodeInfoModel.json | 43 +++ MeshtasticClient/Resources/packets.json | 301 +++++++++--------- MeshtasticClient/Views/ContentView.swift | 6 + .../Views/Devices/DeviceDetail.swift | 1 - .../Views/Devices/DeviceMap.swift | 36 ++- .../Views/Helpers/CircleImage.swift | 1 + .../Views/Helpers/CircleText.swift | 26 ++ .../Views/Messages/Messages.swift | 8 +- MeshtasticClient/Views/Nodes/NodeDetail.swift | 129 ++++++++ MeshtasticClient/Views/Nodes/NodeList.swift | 41 +++ MeshtasticClient/Views/Nodes/NodeRow.swift | 49 +++ 18 files changed, 647 insertions(+), 227 deletions(-) create mode 100644 MeshtasticClient/Helpers/HelperFunctions.swift create mode 100644 MeshtasticClient/Model/NodeInfoModel.swift create mode 100644 MeshtasticClient/Resources/nodeInfoModel.json create mode 100644 MeshtasticClient/Views/Helpers/CircleText.swift create mode 100644 MeshtasticClient/Views/Nodes/NodeDetail.swift create mode 100644 MeshtasticClient/Views/Nodes/NodeList.swift create mode 100644 MeshtasticClient/Views/Nodes/NodeRow.swift diff --git a/Meshtastic Client.xcodeproj/project.pbxproj b/Meshtastic Client.xcodeproj/project.pbxproj index 8870ecb8..21537c54 100644 --- a/Meshtastic Client.xcodeproj/project.pbxproj +++ b/Meshtastic Client.xcodeproj/project.pbxproj @@ -7,7 +7,12 @@ objects = { /* Begin PBXBuildFile section */ - DD8E565A26EE85E3002CD952 /* MeshtasticClient.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD8E565826EE85E3002CD952 /* MeshtasticClient.xcdatamodeld */; }; + DD47E3CC26F0E51D00029299 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CB26F0E51D00029299 /* NodeDetail.swift */; }; + DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; }; + DD47E3D026F1073F00029299 /* NodeRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CF26F1073F00029299 /* NodeRow.swift */; }; + DD47E3D226F1210600029299 /* HelperFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D126F1210600029299 /* HelperFunctions.swift */; }; + DD47E3D626F17ED900029299 /* CircleText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3D526F17ED900029299 /* CircleText.swift */; }; + DD7AA3F326F05C120077AF76 /* NodeInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7AA3F226F05C120077AF76 /* NodeInfoModel.swift */; }; DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; }; DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */; }; DDAF8C5B26ED08D30058C060 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = DDAF8C5A26ED08D30058C060 /* SwiftProtobuf */; }; @@ -59,7 +64,13 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - DD8E565926EE85E3002CD952 /* MeshtasticClient.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MeshtasticClient.xcdatamodel; sourceTree = ""; }; + DD47E3CB26F0E51D00029299 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = ""; }; + DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = ""; }; + DD47E3CF26F1073F00029299 /* NodeRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeRow.swift; sourceTree = ""; }; + DD47E3D126F1210600029299 /* HelperFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperFunctions.swift; sourceTree = ""; }; + DD47E3D526F17ED900029299 /* CircleText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleText.swift; sourceTree = ""; }; + DD7AA3F226F05C120077AF76 /* NodeInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoModel.swift; sourceTree = ""; }; + DD7AA3F426F05D660077AF76 /* nodeInfoModel.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = nodeInfoModel.json; sourceTree = ""; }; DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = ""; }; DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mesh.pb.swift; sourceTree = ""; }; DDAF8C5C26ED09490058C060 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = ""; }; @@ -125,6 +136,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + DD47E3CA26F0E50300029299 /* Nodes */ = { + isa = PBXGroup; + children = ( + DD47E3CB26F0E51D00029299 /* NodeDetail.swift */, + DD47E3CD26F103C600029299 /* NodeList.swift */, + DD47E3CF26F1073F00029299 /* NodeRow.swift */, + ); + path = Nodes; + sourceTree = ""; + }; DDAF8C5626ED07740058C060 /* Protobufs */ = { isa = PBXGroup; children = ( @@ -206,6 +227,7 @@ DDC2E18726CE24E40042C5E4 /* Views */ = { isa = PBXGroup; children = ( + DD47E3CA26F0E50300029299 /* Nodes */, DDC2E18C26CE25B00042C5E4 /* Devices */, DDC2E18B26CE25A70042C5E4 /* Messages */, DDC2E18E26CE25FE0042C5E4 /* ContentView.swift */, @@ -217,10 +239,10 @@ DDC2E18826CE24EE0042C5E4 /* Model */ = { isa = PBXGroup; children = ( - DD8E565826EE85E3002CD952 /* MeshtasticClient.xcdatamodeld */, DDC2E19C26CE27580042C5E4 /* ModelData.swift */, DDC2E19E26CE27630042C5E4 /* Device.swift */, DDAF8C6F26ED1DD20058C060 /* Device2.swift */, + DD7AA3F226F05C120077AF76 /* NodeInfoModel.swift */, ); path = Model; sourceTree = ""; @@ -231,6 +253,7 @@ DDC2E18A26CE25690042C5E4 /* deviceData.json */, DDC2E1AA26DD89EC0042C5E4 /* packets.json */, DDAF8C7126ED2AD80058C060 /* nodeInfo.json */, + DD7AA3F426F05D660077AF76 /* nodeInfoModel.json */, ); path = Resources; sourceTree = ""; @@ -259,6 +282,7 @@ isa = PBXGroup; children = ( DDC2E19A26CE27150042C5E4 /* CircleImage.swift */, + DD47E3D526F17ED900029299 /* CircleText.swift */, ); path = Helpers; sourceTree = ""; @@ -269,6 +293,7 @@ DDAF8C5226EB1DF10058C060 /* BLEManager.swift */, DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */, DDAF8C6D26ED19040058C060 /* Extensions.swift */, + DD47E3D126F1210600029299 /* HelperFunctions.swift */, ); path = Helpers; sourceTree = ""; @@ -411,9 +436,12 @@ buildActionMask = 2147483647; files = ( DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */, + DD47E3CC26F0E51D00029299 /* NodeDetail.swift in Sources */, DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */, DDAF8C7026ED1DD20058C060 /* Device2.swift in Sources */, + DD7AA3F326F05C120077AF76 /* NodeInfoModel.swift in Sources */, DDC2E19F26CE27630042C5E4 /* Device.swift in Sources */, + DD47E3D226F1210600029299 /* HelperFunctions.swift in Sources */, DDC2E19926CE26940042C5E4 /* DeviceMap.swift in Sources */, DDC2E19526CE26760042C5E4 /* DeviceDetail.swift in Sources */, DDAF8C5F26ED09B50058C060 /* radioconfig.pb.swift in Sources */, @@ -425,9 +453,11 @@ DDAF8C6B26ED0DD80058C060 /* environmental_measurement.pb.swift in Sources */, DDAF8C6226ED0A230058C060 /* mqtt.pb.swift in Sources */, DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */, + DD47E3CE26F103C600029299 /* NodeList.swift in Sources */, + DD47E3D026F1073F00029299 /* NodeRow.swift in Sources */, + DD47E3D626F17ED900029299 /* CircleText.swift in Sources */, DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */, DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */, - DD8E565A26EE85E3002CD952 /* MeshtasticClient.xcdatamodeld in Sources */, DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */, DDC2E19326CE266B0042C5E4 /* DeviceHome.swift in Sources */, DDC2E19B26CE27150042C5E4 /* CircleImage.swift in Sources */, @@ -773,19 +803,6 @@ productName = SwiftProtobuf; }; /* End XCSwiftPackageProductDependency section */ - -/* Begin XCVersionGroup section */ - DD8E565826EE85E3002CD952 /* MeshtasticClient.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - DD8E565926EE85E3002CD952 /* MeshtasticClient.xcdatamodel */, - ); - currentVersion = DD8E565926EE85E3002CD952 /* MeshtasticClient.xcdatamodel */; - path = MeshtasticClient.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; - }; -/* End XCVersionGroup section */ }; rootObject = DDC2E14C26CE248E0042C5E4 /* Project object */; } diff --git a/MeshtasticClient/Helpers/BLEManager.swift b/MeshtasticClient/Helpers/BLEManager.swift index 476711d2..aa18b0d6 100644 --- a/MeshtasticClient/Helpers/BLEManager.swift +++ b/MeshtasticClient/Helpers/BLEManager.swift @@ -1,6 +1,7 @@ import Foundation import CoreData import CoreBluetooth +import SwiftUI struct Peripheral: Identifiable { let id: String @@ -20,7 +21,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph @Published var peripheralArray = [CBPeripheral]() private var rssiArray = [NSNumber]() private var timer = Timer() - @Published var isSwitchedOn = false @Published var peripherals = [Peripheral]() @@ -230,6 +230,17 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph print("Save a myInfo") do { print(try decodedInfo.myInfo.jsonString()) + // let node = MyInfoEntity(context: persistentContainer.viewContext) + // node.myNodeNum = Int64(decodedInfo.myInfo.myNodeNum) + + // node.num = Int(decodedInfo.myInfo.myNodeNum) + // node.user?.hwModel = decodedInfo.myInfo.hwModelDeprecated + //node.lastHeard + // node.snr + + // node.user?.shortName decodedInfo.myInfo. + + // try persistentContainer.viewContext.save() } catch { fatalError("Failed to decode json") } @@ -239,7 +250,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph { print("Save a nodeInfo") do { - let node = print(try decodedInfo.nodeInfo.jsonString()) } catch { fatalError("Failed to decode json") diff --git a/MeshtasticClient/Helpers/Extensions.swift b/MeshtasticClient/Helpers/Extensions.swift index 5e9b5960..4de6f3ec 100644 --- a/MeshtasticClient/Helpers/Extensions.swift +++ b/MeshtasticClient/Helpers/Extensions.swift @@ -1,4 +1,5 @@ import Foundation +import SwiftUI extension Data { @@ -8,6 +9,13 @@ extension Data } } +extension Date +{ + static var currentTimeStamp: Int64 + { + return Int64(Date().timeIntervalSince1970 * 1000) + } +} extension String { @@ -36,11 +44,40 @@ extension String } +typealias NodeColor = Int -extension Date -{ - static var currentTimeStamp: Int64 - { - return Int64(Date().timeIntervalSince1970 * 1000) +extension NodeColor { + // a color to represent an individual meshtastic device +var color: Color { + switch self { + case 0: + return Color(.red) + case 1: + return Color(.blue) + case 2: + return Color(.yellow) + case 3: + return Color(.green) + case 4: + return Color(.purple) + case 5: + return Color(.systemPink) + case 6: + return Color(.systemTeal) + case 7: + return Color(.black) + case 8: + return Color(.gray) + case 9: + return Color(.brown) + case 10: + return Color(.magenta) + case 11: + return Color(.orange) + case 12: + return Color(.cyan) + default: + return Color(.blue) + } } } diff --git a/MeshtasticClient/Helpers/HelperFunctions.swift b/MeshtasticClient/Helpers/HelperFunctions.swift new file mode 100644 index 00000000..16b5bc61 --- /dev/null +++ b/MeshtasticClient/Helpers/HelperFunctions.swift @@ -0,0 +1,11 @@ +// +// HelperFunctions.swift +// MeshtasticClient +// +// Created by Garth Vander Houwen on 9/14/21. +// + +import Foundation +import SwiftUI + + diff --git a/MeshtasticClient/Model/ModelData.swift b/MeshtasticClient/Model/ModelData.swift index 77097466..aac3c578 100644 --- a/MeshtasticClient/Model/ModelData.swift +++ b/MeshtasticClient/Model/ModelData.swift @@ -9,6 +9,8 @@ import Combine final class ModelData: ObservableObject { @Published var devices: [Device] = load("deviceData.json") + @Published var nodes: [NodeInfoModel] = load("packets.json") + var nearby: [Device] { devices } diff --git a/MeshtasticClient/Model/NodeInfoModel.swift b/MeshtasticClient/Model/NodeInfoModel.swift new file mode 100644 index 00000000..2df777a9 --- /dev/null +++ b/MeshtasticClient/Model/NodeInfoModel.swift @@ -0,0 +1,37 @@ +// +// NodeInfo.swift +// MeshtasticClient +// +// Created by Garth Vander Houwen on 9/13/21. +// + +import Foundation +import SwiftUI +import CoreLocation + +struct NodeInfoModel: Hashable, Codable, Identifiable { + + let id = UUID() + var num: UInt32 + + var user: User + struct User: Hashable, Codable, Identifiable { + var id: String + var longName: String + var shortName: String + var macaddr: String + var hwModel: String + } + + var position: Position + struct Position: Hashable, Codable { + var latitudeI: Int32? + var longitudeI: Int32? + var altitude: Int32? + var batteryLevel: Int32? + var time: Int32? + } + + var lastHeard: Double + var snr: Double? +} diff --git a/MeshtasticClient/Resources/nodeInfo.json b/MeshtasticClient/Resources/nodeInfo.json index 3c121b62..7950c36b 100644 --- a/MeshtasticClient/Resources/nodeInfo.json +++ b/MeshtasticClient/Resources/nodeInfo.json @@ -1,38 +1,43 @@ -[{ - "nodeInfo": { - "num":3186677820, +[ + { + "num":2792101487, "user":{ - "id":"!bdf0d83c", - "longName":"Garth Vander Houwen", - "shortName":"GVH", - "macaddr":"fJ698Ng8", - "hwModel":"TBEAM" + "id":"!a66c166f", + "longName":"RAK Solar 2", + "shortName":"RS2", + "macaddr":"8eambBZv", + "hwModel":"RAK4631" }, "position":{ - "latitudeI":476040257, - "longitudeI":-1221545412, - "altitude":11, - "batteryLevel":100, - "time":1631384279 - }, - "lastHeard":1631384279 + "batteryLevel":68}, + "lastHeard":1631593661 + } }, { - "nodeInfo":{ - "num":2792101487, - "user":{ - "id":"!a66c166f", - "longName":"RAK Solar 2", - "shortName":"RS2", - "macaddr":"8eambBZv", - "hwModel":"RAK4631" - }, - "position":{ - "batteryLevel":88, - "time":1631297585 - }, - "lastHeard":1631383493, - "snr":12.0 - } - } -}] + "num":1000569662, + "user":{ + "id":"!3ba37b3e", + "longName":"RAK Solar 1", + "shortName":"RS1", + "macaddr":"1Kc7o3s+", + "hwModel":"RAK4631" + }, + "position":{ + "latitudeI":476021390, + "longitudeI":-1221532609, + "altitude":71, + "batteryLevel":70, + "time":1629314497 + }, + "lastHeard":1629392801, + "snr":5.25 + }, + {"num":1133445808,"user":{"id":"!438f02b0","longName":"RAK DEV NODE","shortName":"RDN","macaddr":"92VDjwKw","hwModel":"RAK4631"},"position":{"latitudeI":476022071,"longitudeI":-1221533607,"altitude":91,"batteryLevel":88,"time":1629391435},"lastHeard":1629393577,"snr":4.5}, + {"num":84682040,"user":{"id":"!050c2538","longName":"Yellow Beam","shortName":"YB","macaddr":"PGEFDCU4","hwModel":"TBEAM"},"position":{"latitudeI":476020703,"longitudeI":-1221531973,"altitude":79,"batteryLevel":1,"time":1631593233},"lastHeard":1631593034,"snr":5.0}, + {"num":2354994191,"user":{"id":"!8c5e5c0f","longName":"RAK Solar 3","shortName":"RS3","macaddr":"+GKMXlwP","hwModel":"RAK4631"},"position":{"batteryLevel":91,"time":1629263310},"lastHeard":1629393330,"snr":4.5}, + {"num":2718727166,"user":{"id":"!a20c7bfe","longName":"RAK Large Node","shortName":"RLN","macaddr":"zXSiDHv+","hwModel":"RAK4631"},"position":{"batteryLevel":32,"time":1629061939},"lastHeard":1629102457,"snr":-17.5}, + {"num":84681200,"user":{"id":"!050c21f0","longName":"Unknown 21f0","shortName":"?F0","macaddr":"PGEFDCHw","hwModel":"TBEAM"},"position":{"latitudeI":476022115,"longitudeI":-1221531952,"altitude":85,"batteryLevel":100,"time":1631593830},"lastHeard":1631593584,"snr":6.75}, + {"num":2930161432,"user":{"id":"!aea6b718","longName":"Unknown b718","shortName":"?18","macaddr":"TBGuprcY","hwModel":"TBEAM"},"position":{"latitudeI":476020757,"longitudeI":-1221533124,"altitude":56,"batteryLevel":100,"time":1631593558},"lastHeard":1631593554,"snr":6.75}, + {"num":4064637200,"user":{"id":"!f2457110","longName":"Unknown 7110","shortName":"?10","macaddr":"CDryRXEQ","hwModel":"TBEAM"},"position":{"latitudeI":476021534,"longitudeI":-1221533621,"altitude":49,"batteryLevel":100,"time":1631593778},"lastHeard":1631593533,"snr":7.0} + +] diff --git a/MeshtasticClient/Resources/nodeInfoModel.json b/MeshtasticClient/Resources/nodeInfoModel.json new file mode 100644 index 00000000..7950c36b --- /dev/null +++ b/MeshtasticClient/Resources/nodeInfoModel.json @@ -0,0 +1,43 @@ +[ + { + "num":2792101487, + "user":{ + "id":"!a66c166f", + "longName":"RAK Solar 2", + "shortName":"RS2", + "macaddr":"8eambBZv", + "hwModel":"RAK4631" + }, + "position":{ + "batteryLevel":68}, + "lastHeard":1631593661 + } + }, + { + "num":1000569662, + "user":{ + "id":"!3ba37b3e", + "longName":"RAK Solar 1", + "shortName":"RS1", + "macaddr":"1Kc7o3s+", + "hwModel":"RAK4631" + }, + "position":{ + "latitudeI":476021390, + "longitudeI":-1221532609, + "altitude":71, + "batteryLevel":70, + "time":1629314497 + }, + "lastHeard":1629392801, + "snr":5.25 + }, + {"num":1133445808,"user":{"id":"!438f02b0","longName":"RAK DEV NODE","shortName":"RDN","macaddr":"92VDjwKw","hwModel":"RAK4631"},"position":{"latitudeI":476022071,"longitudeI":-1221533607,"altitude":91,"batteryLevel":88,"time":1629391435},"lastHeard":1629393577,"snr":4.5}, + {"num":84682040,"user":{"id":"!050c2538","longName":"Yellow Beam","shortName":"YB","macaddr":"PGEFDCU4","hwModel":"TBEAM"},"position":{"latitudeI":476020703,"longitudeI":-1221531973,"altitude":79,"batteryLevel":1,"time":1631593233},"lastHeard":1631593034,"snr":5.0}, + {"num":2354994191,"user":{"id":"!8c5e5c0f","longName":"RAK Solar 3","shortName":"RS3","macaddr":"+GKMXlwP","hwModel":"RAK4631"},"position":{"batteryLevel":91,"time":1629263310},"lastHeard":1629393330,"snr":4.5}, + {"num":2718727166,"user":{"id":"!a20c7bfe","longName":"RAK Large Node","shortName":"RLN","macaddr":"zXSiDHv+","hwModel":"RAK4631"},"position":{"batteryLevel":32,"time":1629061939},"lastHeard":1629102457,"snr":-17.5}, + {"num":84681200,"user":{"id":"!050c21f0","longName":"Unknown 21f0","shortName":"?F0","macaddr":"PGEFDCHw","hwModel":"TBEAM"},"position":{"latitudeI":476022115,"longitudeI":-1221531952,"altitude":85,"batteryLevel":100,"time":1631593830},"lastHeard":1631593584,"snr":6.75}, + {"num":2930161432,"user":{"id":"!aea6b718","longName":"Unknown b718","shortName":"?18","macaddr":"TBGuprcY","hwModel":"TBEAM"},"position":{"latitudeI":476020757,"longitudeI":-1221533124,"altitude":56,"batteryLevel":100,"time":1631593558},"lastHeard":1631593554,"snr":6.75}, + {"num":4064637200,"user":{"id":"!f2457110","longName":"Unknown 7110","shortName":"?10","macaddr":"CDryRXEQ","hwModel":"TBEAM"},"position":{"latitudeI":476021534,"longitudeI":-1221533621,"altitude":49,"batteryLevel":100,"time":1631593778},"lastHeard":1631593533,"snr":7.0} + +] diff --git a/MeshtasticClient/Resources/packets.json b/MeshtasticClient/Resources/packets.json index 905da8a7..d319a099 100644 --- a/MeshtasticClient/Resources/packets.json +++ b/MeshtasticClient/Resources/packets.json @@ -1,149 +1,152 @@ -[ - { - "from":1133445808, - "to":1133445808, - "decoded":{ - "portnum":"ADMIN_APP", - "payload":"b"":\\x04\\x08\\x01\\x12\\x00", - "requestId":1506683854, - "admin":{ - "getChannelResponse":{ - "index":1, - "settings":{ - - } - }, - "raw":"get_channel_response"{ - "index":1 settings{ - - } - } - } - }, - "id":1481765942, - "rxTime":1630360112, - "hopLimit":3, - "priority":"RELIABLE", - "raw":"from":"1133445808 to":1133445808 decoded{ - "portnum":"ADMIN_APP payload":":\\004\\010\\001\\022\\000""request_id":1506683854 - }"id":"1481765942 rx_time":"1630360112 hop_limit":"3 priority":"RELIABLE", - "fromId":"!438f02b0", - "toId":"!438f02b0" - }{ - "num":1133445808, - "user":{ - "id":"!438f02b0", - "longName":"RAK DEV NODE", - "shortName":"RDN", - "macaddr":"92VDjwKw", - "hwModel":"RAK4631", - "raw":"id":"!438f02b0""long_name":"RAK DEV NODE""short_name":"RDN""macaddr":"\\367eC\\217\\002\\260""hw_model":RAK4631 - }, - "position":{ - "latitudeI":476021540, - "longitudeI":-1221532059, - "altitude":111, - "time":1630360110, - "latitude":47.602154, - "longitude":-122.15320589999999 - }, - "lastHeard":1630360100, - "lastReceived":{ - "from":1133445808, - "to":4294967295, - "decoded":{ - "portnum":"NODEINFO_APP", - "payload":"b""\n\t!438f02b0\\x12\\x0cRAK DEV NODE\\x1a\\x03RDN\"\\x06\\xf7eC\\x8f\\x02\\xb00\t", - "wantResponse":true, - "user":{ - "id":"!438f02b0", - "longName":"RAK DEV NODE", - "shortName":"RDN", - "macaddr":"92VDjwKw", - "hwModel":"RAK4631", - "raw":"id":"!438f02b0""long_name":"RAK DEV NODE""short_name":"RDN""macaddr":"\\367eC\\217\\002\\260""hw_model":RAK4631 - } - }, - "id":1481765939, - "rxTime":1630360100, - "hopLimit":3, - "priority":"BACKGROUND", - "raw":"from":"1133445808 to":4294967295 decoded{ - "portnum":"NODEINFO_APP payload":"\n\t!438f02b0\\022\\014RAK DEV NODE\\032\\003RDN\"\\006\\367eC\\217\\002\\2600\t""want_response":true - }"id":"1481765939 rx_time":"1630360100 hop_limit":"3 priority":"BACKGROUND", - "fromId":"!438f02b0", - "toId":"^all" - }, - "snr":"None", - "hopLimit":3 - }{ - "num":1000569662, - "user":{ - "id":"!3ba37b3e", - "longName":"RAK Solar 1", - "shortName":"RS1", - "macaddr":"1Kc7o3s+", - "hwModel":"RAK4631" - }, - "position":{ - "latitudeI":476022865, - "longitudeI":-1221531705, - "altitude":81, - "batteryLevel":4, - "time":1628091161, - "latitude":47.6022865, - "longitude":-122.15317049999999 - }, - "lastHeard":1630234276, - "snr":5.25 - }{ - "num":2792101487, - "user":{ - "id":"!a66c166f", - "longName":"RAK Solar 2", - "shortName":"RS2", - "macaddr":"8eambBZv", - "hwModel":"RAK4631" - }, - "position":{ - "batteryLevel":56, - "time":1628096176 - }, - "lastHeard":1628231149, - "snr":4.75 - }{ - "num":84682040, - "user":{ - "id":"!050c2538", - "longName":"Yellow Beam", - "shortName":"YB", - "macaddr":"PGEFDCU4", - "hwModel":"TBEAM" - }, - "position":{ - "latitudeI":476020441, - "longitudeI":-1221533598, - "altitude":65, - "batteryLevel":100, - "time":1628405738, - "latitude":47.6020441, - "longitude":-122.15335979999999 - }, - "lastHeard":1628405744, - "snr":4.5 - }{ - "num":2354994191, - "user":{ - "id":"!8c5e5c0f", - "longName":"RAK Solar 3", - "shortName":"RS3", - "macaddr":"+GKMXlwP", - "hwModel":"RAK4631" - }, - "position":{ - "batteryLevel":62 - }, - "lastHeard":1630252441, - "snr":4.75 - } -] +[{ + "num": 2792101487, + "user": { + "id": "!a66c166f", + "longName": "RAK Solar 2", + "shortName": "RS2", + "macaddr": "8eambBZv", + "hwModel": "RAK4631" + }, + "position": { + "batteryLevel": 68 + }, + "lastHeard": 1631593661 +}, { + "num": 1000569662, + "user": { + "id": "!3ba37b3e", + "longName": "RAK Solar 1", + "shortName": "RS1", + "macaddr": "1Kc7o3s+", + "hwModel": "RAK4631" + }, + "position": { + "latitudeI": 476021390, + "longitudeI": -1221532609, + "altitude": 71, + "batteryLevel": 70, + "time": 1629314497 + }, + "lastHeard": 1629392801, + "snr": 5.25 +}, { + "num": 1133445808, + "user": { + "id": "!438f02b0", + "longName": "RAK DEV NODE", + "shortName": "RDN", + "macaddr": "92VDjwKw", + "hwModel": "RAK4631" + }, + "position": { + "latitudeI": 476022071, + "longitudeI": -1221533607, + "altitude": 91, + "batteryLevel": 88, + "time": 1629391435 + }, + "lastHeard": 1629393577, + "snr": 4.5 +}, { + "num": 84682040, + "user": { + "id": "!050c2538", + "longName": "Yellow Beam", + "shortName": "YB", + "macaddr": "PGEFDCU4", + "hwModel": "TBEAM" + }, + "position": { + "latitudeI": 476020703, + "longitudeI": -1221531973, + "altitude": 79, + "batteryLevel": 1, + "time": 1631593233 + }, + "lastHeard": 1631593034, + "snr": 5.0 +}, { + "num": 2354994191, + "user": { + "id": "!8c5e5c0f", + "longName": "RAK Solar 3", + "shortName": "RS3", + "macaddr": "+GKMXlwP", + "hwModel": "RAK4631" + }, + "position": { + "batteryLevel": 91, + "time": 1629263310 + }, + "lastHeard": 1629393330, + "snr": 4.5 +}, { + "num": 2718727166, + "user": { + "id": "!a20c7bfe", + "longName": "RAK Large Node", + "shortName": "RLN", + "macaddr": "zXSiDHv+", + "hwModel": "RAK4631" + }, + "position": { + "batteryLevel": 32, + "time": 1629061939 + }, + "lastHeard": 1629102457, + "snr": -17.5 +}, { + "num": 84681200, + "user": { + "id": "!050c21f0", + "longName": "Unknown 21f0", + "shortName": "?F0", + "macaddr": "PGEFDCHw", + "hwModel": "TBEAM" + }, + "position": { + "latitudeI": 476022115, + "longitudeI": -1221531952, + "altitude": 85, + "batteryLevel": 100, + "time": 1631593830 + }, + "lastHeard": 1631593584, + "snr": 6.75 +}, { + "num": 2930161432, + "user": { + "id": "!aea6b718", + "longName": "Unknown b718", + "shortName": "?18", + "macaddr": "TBGuprcY", + "hwModel": "TBEAM" + }, + "position": { + "latitudeI": 476020757, + "longitudeI": -1221533124, + "altitude": 56, + "batteryLevel": 100, + "time": 1631593558 + }, + "lastHeard": 1631593554, + "snr": 6.75 +}, { + "num": 4064637200, + "user": { + "id": "!f2457110", + "longName": "Unknown 7110", + "shortName": "?10", + "macaddr": "CDryRXEQ", + "hwModel": "TBEAM" + }, + "position": { + "latitudeI": 476021534, + "longitudeI": -1221533621, + "altitude": 49, + "batteryLevel": 100, + "time": 1631593778 + }, + "lastHeard": 1631593533, + "snr": 7.0 +}] diff --git a/MeshtasticClient/Views/ContentView.swift b/MeshtasticClient/Views/ContentView.swift index 7d322033..6aee7886 100644 --- a/MeshtasticClient/Views/ContentView.swift +++ b/MeshtasticClient/Views/ContentView.swift @@ -14,6 +14,7 @@ struct ContentView: View { case featured case list case ble + case nodes } var body: some View { @@ -33,6 +34,11 @@ struct ContentView: View { Label("Devices", systemImage: "flipphone") } .tag(Tab.devices) + NodeList() + .tabItem { + Label("Nodes", systemImage: "flipphone") + } + .tag(Tab.nodes) DeviceBLE() .tabItem { Label("Bluetooth", systemImage: "dot.radiowaves.left.and.right") diff --git a/MeshtasticClient/Views/Devices/DeviceDetail.swift b/MeshtasticClient/Views/Devices/DeviceDetail.swift index 2a2b7065..f92e7fe8 100644 --- a/MeshtasticClient/Views/Devices/DeviceDetail.swift +++ b/MeshtasticClient/Views/Devices/DeviceDetail.swift @@ -17,7 +17,6 @@ struct DeviceDetail: View { var deviceIndex: Int { modelData.devices.firstIndex(where: { $0.id == device.id })! } - struct MapLocation: Identifiable { let id = UUID() let name: String diff --git a/MeshtasticClient/Views/Devices/DeviceMap.swift b/MeshtasticClient/Views/Devices/DeviceMap.swift index eeda2dfd..1a05dfbb 100644 --- a/MeshtasticClient/Views/Devices/DeviceMap.swift +++ b/MeshtasticClient/Views/Devices/DeviceMap.swift @@ -40,21 +40,25 @@ struct DeviceMap: View { MapLocation(name: devices[3].shortName, coordinate: CLLocationCoordinate2D(latitude: devices[3].position.latitude, longitude: devices[3].position.longitude)) ] - ZStack { - Map(coordinateRegion: regionBinding, - interactionModes: [.all], - showsUserLocation: true, - userTrackingMode: .constant(.follow), annotationItems: annotations) { location in - - MapAnnotation( - coordinate: location.coordinate, - content: { - Text(location.name).font(.caption2).foregroundColor(.white) - .background(Circle() - .fill(Color.blue) - .frame(width: 40, height: 40)) } - ) - }.frame(maxHeight:.infinity) - } + NavigationView { + ZStack { + Map(coordinateRegion: regionBinding, + interactionModes: [.all], + showsUserLocation: true, + userTrackingMode: .constant(.follow), annotationItems: annotations) { location in + + MapAnnotation( + coordinate: location.coordinate, + content: { + Text(location.name).font(.caption2).foregroundColor(.white) + .background(Circle() + .fill(Color.blue) + .frame(width: 40, height: 40)) } + ) + }.frame(maxHeight:.infinity) + } + .navigationTitle("Mesh Map") + .navigationBarTitleDisplayMode(.inline) + }.navigationViewStyle(StackNavigationViewStyle()) } } diff --git a/MeshtasticClient/Views/Helpers/CircleImage.swift b/MeshtasticClient/Views/Helpers/CircleImage.swift index 34dcbade..f8e1ef04 100644 --- a/MeshtasticClient/Views/Helpers/CircleImage.swift +++ b/MeshtasticClient/Views/Helpers/CircleImage.swift @@ -12,6 +12,7 @@ struct CircleImage: View { var body: some View { image + .resizable() .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/) .overlay(Circle().stroke(Color.white, lineWidth: 4)) .shadow(radius: 7) diff --git a/MeshtasticClient/Views/Helpers/CircleText.swift b/MeshtasticClient/Views/Helpers/CircleText.swift new file mode 100644 index 00000000..84d5b15d --- /dev/null +++ b/MeshtasticClient/Views/Helpers/CircleText.swift @@ -0,0 +1,26 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +A view that clips an image to a circle and adds a stroke and shadow. +*/ + +import SwiftUI + +struct CircleText: View { + var text: String + + var body: some View { + + Text(text).font(.subheadline).foregroundColor(.white) + .background(Circle() + .fill(Color.blue) + .frame(width: 40, height: 40, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)) + } +} + +struct CircleText_Previews: PreviewProvider { + static var previews: some View { + CircleText(text: "RDN") + } +} diff --git a/MeshtasticClient/Views/Messages/Messages.swift b/MeshtasticClient/Views/Messages/Messages.swift index daeb8d52..10c79910 100644 --- a/MeshtasticClient/Views/Messages/Messages.swift +++ b/MeshtasticClient/Views/Messages/Messages.swift @@ -4,7 +4,7 @@ import CoreLocation struct Messages: View { var body: some View { - ZStack { + NavigationView { List { HStack { @@ -50,8 +50,8 @@ struct Messages: View { Spacer() } } - } - .navigationTitle("Broadcast Channel") - } + }.navigationTitle("Broadcast Channel") + .navigationBarTitleDisplayMode(.inline) + }.navigationViewStyle(StackNavigationViewStyle()) } } diff --git a/MeshtasticClient/Views/Nodes/NodeDetail.swift b/MeshtasticClient/Views/Nodes/NodeDetail.swift new file mode 100644 index 00000000..38415786 --- /dev/null +++ b/MeshtasticClient/Views/Nodes/NodeDetail.swift @@ -0,0 +1,129 @@ + +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +A view showing the details for a device. +*/ + +import SwiftUI +import MapKit +import CoreLocation +import CoreBluetooth + +struct NodeDetail: View { + + @EnvironmentObject var modelData: ModelData + var node: NodeInfoModel + + var nodeIndex: Int { + modelData.nodes.firstIndex(where: { $0.id == node.id })! + } + + struct MapLocation: Identifiable { + let id = UUID() + let name: String + let coordinate: CLLocationCoordinate2D + } + var body: some View { + let location = LocationHelper.currentLocation + let currentCoordinatePosition = CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude) + let regionBinding = Binding( + get: { + MKCoordinateRegion(center: currentCoordinatePosition, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)) + }, + set: { _ in } + ) + + GeometryReader { bounds in + + VStack { + + // Map or Device Image + if(node.position.latitudeI != nil && node.position.latitudeI! > 0) { + Map(coordinateRegion: regionBinding, showsUserLocation: true, userTrackingMode: .constant(.follow)) + + .frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2) + } + else + { + Image(node.user.hwModel.lowercased()) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: bounds.size.width, height: bounds.size.height / 2) + } + ScrollView { + HStack { + Spacer() + Image(systemName: "flipphone").font(.largeTitle).foregroundColor(.blue) + Text("Model: " + String(node.user.hwModel)).font(.title) + Spacer() + }.padding() + Divider() + HStack { + Image(systemName: "antenna.radiowaves.left.and.right").font(.title2).foregroundColor(.blue) + VStack(alignment: .center) { + + Text("SNR").font(.title3) + Text(String(node.snr!)).font(.title3).foregroundColor(.gray) + } + Divider() + VStack(alignment: .center) { + + Text("AKA").font(.title3) + Text(node.user.shortName).font(.title3).foregroundColor(.gray) + } + Divider() + VStack(alignment: .leading) { + Text("Battery").font(.title3) + Text(String(node.position.batteryLevel!) + "%").font(.title3).foregroundColor(.gray) + } + }.padding(4) + Divider() + HStack{ + Image(systemName: "clock").font(.title2).foregroundColor(.blue) + let lastHeard = Date(timeIntervalSince1970: node.lastHeard) + Text("Last Heard:").font(.title3) + Text(lastHeard, style: .relative).font(.title3) + Text("ago").font(.title3) + }.padding() + Divider() + HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/, spacing: 14) { + Image(systemName: "mappin").font(.title).foregroundColor(.blue) + VStack(alignment: .leading) { + Text("Latitude").font(.headline) + Text(String(node.position.latitudeI!)).font(.caption).foregroundColor(.gray) + } + VStack(alignment: .leading) { + Text("Longitude").font(.headline) + Text(String(node.position.longitudeI!)).font(.caption).foregroundColor(.gray) + } + VStack(alignment: .leading) { + Text("Altitude").font(.headline) + Text(String(node.position.altitude!) + " m").font(.caption).foregroundColor(.gray) + } + }.padding() + Divider() + HStack { + Spacer() + Image(systemName: "person").font(.title3).foregroundColor(.blue) + Text("Unique Id: " + String(node.user.id)).font(.headline) + Divider() + Image(systemName: "number").font(.title3).foregroundColor(.blue) + Text("Node Num: " + String(node.num)).font(.headline) + Spacer() + }.padding() + } + }.navigationTitle(node.user.longName) + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(trailing: + HStack { + + CircleText(text: node.user.shortName).offset(y: -2) + + } + ) + }.ignoresSafeArea(.all, edges: [.leading, .trailing]) + + } +} diff --git a/MeshtasticClient/Views/Nodes/NodeList.swift b/MeshtasticClient/Views/Nodes/NodeList.swift new file mode 100644 index 00000000..77e2a9d4 --- /dev/null +++ b/MeshtasticClient/Views/Nodes/NodeList.swift @@ -0,0 +1,41 @@ +// +// DeviceHome.swift +// MeshtasticClient +// +// Created by Garth Vander Houwen on 8/7/21. +// + +// Abstract: +// A view showing a list of devices that have been seen on the mesh network + +import SwiftUI + +struct NodeList: View { + @EnvironmentObject var modelData: ModelData + + + var body: some View { + NavigationView { + + List { + + + ForEach(modelData.nodes) { node in + NavigationLink(destination: NodeDetail(node: node)) { + NodeRow(node: node) + } + } + } + .navigationTitle("All Nodes") + + + }.navigationViewStyle(StackNavigationViewStyle()) // Force Full screen master details + } +} + +struct NodeList_Previews: PreviewProvider { + static var previews: some View { + NodeList() + .environmentObject(ModelData()) + } +} diff --git a/MeshtasticClient/Views/Nodes/NodeRow.swift b/MeshtasticClient/Views/Nodes/NodeRow.swift new file mode 100644 index 00000000..39b8b83f --- /dev/null +++ b/MeshtasticClient/Views/Nodes/NodeRow.swift @@ -0,0 +1,49 @@ +// +// DeviceMap.swift +// MeshtasticClient +// +// Created by Garth Vander Houwen on 8/7/21. +// +// Abstract: +// A single row to be displayed in a list of landmarks. + +import SwiftUI + +struct NodeRow: View { + var node: NodeInfoModel + + var body: some View { + HStack { + Image(node.user.hwModel.lowercased()).resizable().frame(width: 150, height: 150) + + VStack(alignment: .leading) { + + Text(node.user.longName).font(.title2) + HStack { + if node.user.hwModel == "TBEAM" || node.user.hwModel == "TLORA" { + Image(systemName: "wifi") + .foregroundColor(.blue).font(.title3) + } + if false { + Image(systemName: "rectangle.connected.to.line.below") + .foregroundColor(.green).font(.title2) + } + } + } + Spacer() + } + } +} + +struct NodeRow_Previews: PreviewProvider { + static var nodes = ModelData().nodes + + static var previews: some View { + Group { + NodeRow(node: nodes[0]) + NodeRow(node: nodes[1]) + NodeRow(node: nodes[2]) + } + .previewLayout(.fixed(width: 300, height: 70)) + } +}