mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
Merge pull request #72 from meshtastic/feature/1.3_Cleanup
Feature/1.3 cleanup
This commit is contained in:
commit
777f7827f5
16 changed files with 675 additions and 791 deletions
|
|
@ -38,6 +38,7 @@
|
|||
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */; };
|
||||
DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = DD9D8F2D2764403B00080993 /* Meshtastic.xcdatamodeld */; };
|
||||
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */; };
|
||||
DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */; };
|
||||
DDAF8C5326EB1DF10058C060 /* BLEManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5226EB1DF10058C060 /* BLEManager.swift */; };
|
||||
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */; };
|
||||
DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAF8C5C26ED09490058C060 /* portnums.pb.swift */; };
|
||||
|
|
@ -57,7 +58,9 @@
|
|||
DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */; };
|
||||
DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC3B273283F411B00AC321C /* LastHeardText.swift */; };
|
||||
DDC4D568275499A500A4208E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC4D567275499A500A4208E /* Persistence.swift */; };
|
||||
DDCA31322826009C00207175 /* PassKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDCA31312826009C00207175 /* PassKit.framework */; };
|
||||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; };
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; };
|
||||
DDE393BA284E535700473991 /* ChannelHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE393B9284E535700473991 /* ChannelHelper.swift */; };
|
||||
DDF924CA26FBB953009FE055 /* ConnectedDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDF924C926FBB953009FE055 /* ConnectedDevice.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
|
@ -110,6 +113,7 @@
|
|||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = "<group>"; };
|
||||
DD9D8F2E2764403B00080993 /* CoreDataSample.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreDataSample.xcdatamodel; sourceTree = "<group>"; };
|
||||
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshPackets.swift; sourceTree = "<group>"; };
|
||||
DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeAnnotation.swift; sourceTree = "<group>"; };
|
||||
DDAF8C5226EB1DF10058C060 /* BLEManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManager.swift; sourceTree = "<group>"; };
|
||||
DDAF8C5726ED07FD0058C060 /* mesh.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = mesh.pb.swift; sourceTree = "<group>"; };
|
||||
DDAF8C5C26ED09490058C060 /* portnums.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = portnums.pb.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -137,6 +141,9 @@
|
|||
DDC3B273283F411B00AC321C /* LastHeardText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LastHeardText.swift; sourceTree = "<group>"; };
|
||||
DDC4D567275499A500A4208E /* Persistence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||
DDCA31312826009C00207175 /* PassKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PassKit.framework; path = System/Library/Frameworks/PassKit.framework; sourceTree = SDKROOT; };
|
||||
DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = "<group>"; };
|
||||
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = "<group>"; };
|
||||
DDE393B9284E535700473991 /* ChannelHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelHelper.swift; sourceTree = "<group>"; };
|
||||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectedDevice.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
|
@ -145,7 +152,6 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
DDCA31322826009C00207175 /* PassKit.framework in Frameworks */,
|
||||
C9697FA527933B8C00250207 /* SQLite in Frameworks */,
|
||||
DD5394FC276993AD00AD86B1 /* SwiftProtobuf in Frameworks */,
|
||||
);
|
||||
|
|
@ -358,6 +364,8 @@
|
|||
DD90860B26F684AF00DC5189 /* BatteryIcon.swift */,
|
||||
DDF924C926FBB953009FE055 /* ConnectedDevice.swift */,
|
||||
DDC3B273283F411B00AC321C /* LastHeardText.swift */,
|
||||
DDA6B2EA28420A7B003E8C16 /* NodeAnnotation.swift */,
|
||||
DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -371,6 +379,7 @@
|
|||
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */,
|
||||
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */,
|
||||
DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */,
|
||||
DDE393B9284E535700473991 /* ChannelHelper.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -380,6 +389,7 @@
|
|||
children = (
|
||||
DDC4D567275499A500A4208E /* Persistence.swift */,
|
||||
DD5394FD276BA0EF00AD86B1 /* PositionEntityExtension.swift */,
|
||||
DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */,
|
||||
);
|
||||
path = Persistence;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -456,7 +466,7 @@
|
|||
TargetAttributes = {
|
||||
DDC2E15326CE248E0042C5E4 = {
|
||||
CreatedOnToolsVersion = 12.5.1;
|
||||
LastSwiftMigration = 1320;
|
||||
LastSwiftMigration = 1340;
|
||||
};
|
||||
DDC2E16926CE248F0042C5E4 = {
|
||||
CreatedOnToolsVersion = 12.5.1;
|
||||
|
|
@ -546,6 +556,7 @@
|
|||
DD4DED9027AD2975004BA27E /* cannedmessages.pb.swift in Sources */,
|
||||
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */,
|
||||
DDAF8C6E26ED19040058C060 /* Extensions.swift in Sources */,
|
||||
DDA6B2EB28420A7B003E8C16 /* NodeAnnotation.swift in Sources */,
|
||||
DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */,
|
||||
DD5394FE276BA0EF00AD86B1 /* PositionEntityExtension.swift in Sources */,
|
||||
DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */,
|
||||
|
|
@ -571,6 +582,7 @@
|
|||
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */,
|
||||
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
|
||||
DD17E5DE277D49D400010EC2 /* storeforward.pb.swift in Sources */,
|
||||
DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */,
|
||||
C9A88B55278B503C00BD810A /* MapViewModule.swift in Sources */,
|
||||
DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */,
|
||||
C9483F6D2773017500998F6B /* MapView.swift in Sources */,
|
||||
|
|
@ -579,6 +591,8 @@
|
|||
DDC3B274283F411B00AC321C /* LastHeardText.swift in Sources */,
|
||||
DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */,
|
||||
DDA6B2E928419CF2003E8C16 /* MeshPackets.swift in Sources */,
|
||||
DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */,
|
||||
DDE393BA284E535700473991 /* ChannelHelper.swift in Sources */,
|
||||
C9A88B57278B559900BD810A /* apponly.pb.swift in Sources */,
|
||||
DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */,
|
||||
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */,
|
||||
|
|
@ -751,7 +765,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
@ -783,7 +797,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = MeshtasticClient/MeshtasticClient.entitlements;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 11;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"MeshtasticClient/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = GCH7VS5Y9R;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
|
|
|||
|
|
@ -194,9 +194,9 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
}
|
||||
}
|
||||
|
||||
let today = Date()
|
||||
let oneMinuteAgo = Calendar.current.date(byAdding: .minute, value: -1, to: today)!
|
||||
peripherals.removeAll(where: { $0.lastUpdate <= oneMinuteAgo})
|
||||
// let today = Date()
|
||||
// let visibleDuration = Calendar.current.date(byAdding: .second, value: -7, to: Date())!
|
||||
// peripherals.removeAll(where: { $0.lastUpdate <= visibleDuration})
|
||||
}
|
||||
|
||||
// Called when a peripheral is connected
|
||||
|
|
@ -296,9 +296,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
|
||||
|
||||
print(invalidatedServices)
|
||||
}
|
||||
|
||||
// MARK: Discover Characteristics Event
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||
|
||||
if let e = error {
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("🚫 BLE didDiscoverCharacteristicsFor error by \(peripheral.name ?? "Unknown") \(e)") }
|
||||
|
|
@ -333,8 +339,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
default:
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
||||
|
|
@ -386,15 +391,51 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
switch decodedInfo.packet.decoded.portnum {
|
||||
|
||||
case .unknownApp:
|
||||
print("MyInfo or NodeInfo")
|
||||
if decodedInfo.myInfo.myNodeNum != 0 {
|
||||
|
||||
let myInfo = myInfoPacket(myInfo: decodedInfo.myInfo, meshLogging: meshLoggingEnabled, context: context!)
|
||||
|
||||
if myInfo != nil {
|
||||
|
||||
self.connectedPeripheral.bitrate = myInfo!.bitrate
|
||||
self.connectedPeripheral.num = myInfo!.myNodeNum
|
||||
lastConnnectionVersion = myInfo?.firmwareVersion ?? myInfo!.firmwareVersion ?? "Unknown"
|
||||
self.connectedPeripheral.firmwareVersion = myInfo!.firmwareVersion ?? "Unknown"
|
||||
self.connectedPeripheral.name = myInfo!.bleName ?? "Unknown"
|
||||
}
|
||||
|
||||
} else if decodedInfo.nodeInfo.num != 0 {
|
||||
|
||||
let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, meshLogging: meshLoggingEnabled, context: context!)
|
||||
|
||||
if nodeInfo != nil {
|
||||
|
||||
self.connectedPeripheral.channelUtilization = decodedInfo.nodeInfo.deviceMetrics.channelUtilization
|
||||
self.connectedPeripheral.airTime = decodedInfo.nodeInfo.deviceMetrics.airUtilTx
|
||||
|
||||
if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo!.num {
|
||||
|
||||
if nodeInfo!.user != nil {
|
||||
|
||||
connectedPeripheral.name = nodeInfo!.user!.longName ?? "Unknown"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Unknown App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
|
||||
}
|
||||
case .textMessageApp:
|
||||
textMessageAppPacket(packet: decodedInfo.packet, connectedNode: (self.connectedPeripheral != nil ? connectedPeripheral.num : 0), meshLogging: meshLoggingEnabled, context: context!)
|
||||
case .remoteHardwareApp:
|
||||
if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
|
||||
case .positionApp:
|
||||
positionPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!)
|
||||
case .waypointApp:
|
||||
if meshLoggingEnabled { MeshLogger.log("ℹ️ MESH PACKET received for Waypoint App UNHANDLED \(try! decodedInfo.packet.jsonString())") }
|
||||
case .nodeinfoApp:
|
||||
nodeInfoPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!)
|
||||
nodeInfoAppPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!)
|
||||
case .routingApp:
|
||||
routingPacket(packet: decodedInfo.packet, meshLogging: meshLoggingEnabled, context: context!)
|
||||
case .adminApp:
|
||||
|
|
@ -425,288 +466,40 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
print("MAX PORT NUM OF 511")
|
||||
}
|
||||
|
||||
// MARK: Incoming MyInfo Packet
|
||||
if decodedInfo.myInfo.myNodeNum != 0 {
|
||||
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.myInfo.myNodeNum))
|
||||
|
||||
do {
|
||||
let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
|
||||
// Not Found Insert
|
||||
if fetchedMyInfo.isEmpty {
|
||||
let myInfo = MyInfoEntity(context: context!)
|
||||
myInfo.myNodeNum = Int64(decodedInfo.myInfo.myNodeNum)
|
||||
myInfo.hasGps = decodedInfo.myInfo.hasGps_p
|
||||
myInfo.bitrate = decodedInfo.myInfo.bitrate
|
||||
self.connectedPeripheral.bitrate = myInfo.bitrate
|
||||
|
||||
// Swift does strings weird, this does work to get the version without the github hash
|
||||
let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".")
|
||||
var version = decodedInfo.myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: decodedInfo.myInfo.firmwareVersion))]
|
||||
version = version.dropLast()
|
||||
myInfo.firmwareVersion = String(version)
|
||||
lastConnnectionVersion = String(version)
|
||||
// MARK: Check for an All / Broadcast User
|
||||
let fetchBCUserRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
|
||||
fetchBCUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(broadcastNodeNum))
|
||||
|
||||
do {
|
||||
let fetchedUser = try context?.fetch(fetchBCUserRequest) as! [UserEntity]
|
||||
|
||||
myInfo.messageTimeoutMsec = Int32(bitPattern: decodedInfo.myInfo.messageTimeoutMsec)
|
||||
myInfo.minAppVersion = Int32(bitPattern: decodedInfo.myInfo.minAppVersion)
|
||||
myInfo.maxChannels = Int32(bitPattern: decodedInfo.myInfo.maxChannels)
|
||||
self.connectedPeripheral.num = myInfo.myNodeNum
|
||||
self.connectedPeripheral.firmwareVersion = myInfo.firmwareVersion ?? "Unknown"
|
||||
self.connectedPeripheral.name = myInfo.bleName ?? "Unknown"
|
||||
|
||||
let fetchBCUserRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "UserEntity")
|
||||
fetchBCUserRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.myInfo.myNodeNum))
|
||||
|
||||
do {
|
||||
let fetchedUser = try context?.fetch(fetchBCUserRequest) as! [UserEntity]
|
||||
|
||||
if fetchedUser.isEmpty {
|
||||
// Save the broadcast user if it does not exist
|
||||
let bcu: UserEntity = UserEntity(context: context!)
|
||||
bcu.shortName = "ALL"
|
||||
bcu.longName = "All - Broadcast"
|
||||
bcu.hwModel = "UNSET"
|
||||
bcu.num = Int64(broadcastNodeNum)
|
||||
bcu.userId = "BROADCASTNODE"
|
||||
print("💾 Saved the All - Broadcast User")
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
print("💥 Error Saving the All - Broadcast User")
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
fetchedMyInfo[0].myNodeNum = Int64(decodedInfo.myInfo.myNodeNum)
|
||||
fetchedMyInfo[0].hasGps = decodedInfo.myInfo.hasGps_p
|
||||
fetchedMyInfo[0].bitrate = decodedInfo.myInfo.bitrate
|
||||
|
||||
let lastDotIndex = decodedInfo.myInfo.firmwareVersion.lastIndex(of: ".")//.lastIndex(of: ".", offsetBy: -1)
|
||||
var version = decodedInfo.myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset:6, in: decodedInfo.myInfo.firmwareVersion))]
|
||||
version = version.dropLast()
|
||||
fetchedMyInfo[0].firmwareVersion = String(version)
|
||||
lastConnnectionVersion = String(version)
|
||||
fetchedMyInfo[0].messageTimeoutMsec = Int32(bitPattern: decodedInfo.myInfo.messageTimeoutMsec)
|
||||
fetchedMyInfo[0].minAppVersion = Int32(bitPattern: decodedInfo.myInfo.minAppVersion)
|
||||
fetchedMyInfo[0].maxChannels = Int32(bitPattern: decodedInfo.myInfo.maxChannels)
|
||||
|
||||
self.connectedPeripheral.num = fetchedMyInfo[0].myNodeNum
|
||||
self.connectedPeripheral.firmwareVersion = fetchedMyInfo[0].firmwareVersion ?? "Unknown"
|
||||
self.connectedPeripheral.name = fetchedMyInfo[0].bleName ?? "Unknown"
|
||||
self.connectedPeripheral.bitrate = fetchedMyInfo[0].bitrate
|
||||
|
||||
}
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
if meshLoggingEnabled { MeshLogger.log("💾 Saved a myInfo for \(peripheral.name ?? String(decodedInfo.myInfo.myNodeNum))") }
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving Core Data MyInfoEntity: \(nsError)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
print("💥 Fetch MyInfo Error")
|
||||
if fetchedUser.isEmpty {
|
||||
// Save the broadcast user if it does not exist
|
||||
let bcu: UserEntity = UserEntity(context: context!)
|
||||
bcu.shortName = "ALL"
|
||||
bcu.longName = "All - Broadcast"
|
||||
bcu.hwModel = "UNSET"
|
||||
bcu.num = Int64(broadcastNodeNum)
|
||||
bcu.userId = "BROADCASTNODE"
|
||||
print("💾 Saved the All - Broadcast User")
|
||||
}
|
||||
|
||||
// MARK: Share Location Position Update Timer
|
||||
// Use context to pass the radio name with the timer
|
||||
// Use a RunLoop to prevent the timer from running on the main UI thread
|
||||
if userSettings?.provideLocation ?? false {
|
||||
|
||||
if self.positionTimer != nil {
|
||||
self.positionTimer!.invalidate()
|
||||
}
|
||||
let context = ["name": "@\(peripheral.name ?? "Unknown")"]
|
||||
self.positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true)
|
||||
RunLoop.current.add(self.positionTimer!, forMode: .common)
|
||||
}
|
||||
} catch {
|
||||
|
||||
print("💥 Error Saving the All - Broadcast User")
|
||||
}
|
||||
|
||||
// MARK: Incoming Node Info Packet
|
||||
if decodedInfo.nodeInfo.num != 0 {
|
||||
|
||||
let fetchNodeRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeRequest.predicate = NSPredicate(format: "num == %lld", Int64(decodedInfo.nodeInfo.num))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedNode = try context?.fetch(fetchNodeRequest) as! [NodeInfoEntity]
|
||||
// Not Found Insert
|
||||
if fetchedNode.isEmpty && decodedInfo.nodeInfo.hasUser {
|
||||
|
||||
let newNode = NodeInfoEntity(context: context!)
|
||||
newNode.id = Int64(decodedInfo.nodeInfo.num)
|
||||
newNode.num = Int64(decodedInfo.nodeInfo.num)
|
||||
|
||||
if decodedInfo.nodeInfo.hasDeviceMetrics {
|
||||
|
||||
let telemetry = TelemetryEntity(context: context!)
|
||||
|
||||
telemetry.batteryLevel = Int32(decodedInfo.nodeInfo.deviceMetrics.batteryLevel)
|
||||
telemetry.voltage = decodedInfo.nodeInfo.deviceMetrics.voltage
|
||||
telemetry.channelUtilization = decodedInfo.nodeInfo.deviceMetrics.channelUtilization
|
||||
self.connectedPeripheral.channelUtilization = telemetry.channelUtilization
|
||||
telemetry.airUtilTx = decodedInfo.nodeInfo.deviceMetrics.airUtilTx
|
||||
self.connectedPeripheral.airTime = decodedInfo.nodeInfo.deviceMetrics.airUtilTx
|
||||
|
||||
var newTelemetries = [TelemetryEntity]()
|
||||
newTelemetries.append(telemetry)
|
||||
newNode.telemetries? = NSOrderedSet(array: newTelemetries)
|
||||
}
|
||||
|
||||
newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.lastHeard)))
|
||||
newNode.snr = decodedInfo.nodeInfo.snr
|
||||
|
||||
if self.connectedPeripheral != nil && self.connectedPeripheral.num == newNode.num {
|
||||
|
||||
if decodedInfo.nodeInfo.hasUser {
|
||||
|
||||
connectedPeripheral.name = decodedInfo.nodeInfo.user.longName
|
||||
}
|
||||
}
|
||||
|
||||
if decodedInfo.nodeInfo.hasUser {
|
||||
|
||||
let newUser = UserEntity(context: context!)
|
||||
newUser.userId = decodedInfo.nodeInfo.user.id
|
||||
newUser.num = Int64(decodedInfo.nodeInfo.num)
|
||||
newUser.longName = decodedInfo.nodeInfo.user.longName
|
||||
newUser.shortName = decodedInfo.nodeInfo.user.shortName
|
||||
newUser.macaddr = decodedInfo.nodeInfo.user.macaddr
|
||||
newUser.hwModel = String(describing: decodedInfo.nodeInfo.user.hwModel).uppercased()
|
||||
newNode.user = newUser
|
||||
}
|
||||
|
||||
let position = PositionEntity(context: context!)
|
||||
position.latitudeI = decodedInfo.nodeInfo.position.latitudeI
|
||||
position.longitudeI = decodedInfo.nodeInfo.position.longitudeI
|
||||
position.altitude = decodedInfo.nodeInfo.position.altitude
|
||||
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.position.time)))
|
||||
|
||||
var newPostions = [PositionEntity]()
|
||||
newPostions.append(position)
|
||||
newNode.positions? = NSOrderedSet(array: newPostions)
|
||||
|
||||
// Look for a MyInfo
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.nodeInfo.num))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
|
||||
if fetchedMyInfo.count > 0 {
|
||||
newNode.myInfo = fetchedMyInfo[0]
|
||||
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("💥 Fetch MyInfo Error")
|
||||
}
|
||||
|
||||
} else if decodedInfo.nodeInfo.hasUser && decodedInfo.nodeInfo.num > 0 {
|
||||
|
||||
fetchedNode[0].id = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].num = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.lastHeard)))
|
||||
fetchedNode[0].snr = decodedInfo.nodeInfo.snr
|
||||
|
||||
if self.connectedPeripheral != nil && self.connectedPeripheral.num == fetchedNode[0].num {
|
||||
|
||||
if decodedInfo.nodeInfo.hasUser {
|
||||
|
||||
self.connectedPeripheral.name = fetchedNode[0].user!.longName ?? "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
if decodedInfo.nodeInfo.hasUser {
|
||||
|
||||
fetchedNode[0].user!.userId = decodedInfo.nodeInfo.user.id
|
||||
fetchedNode[0].user!.num = Int64(decodedInfo.nodeInfo.num)
|
||||
fetchedNode[0].user!.longName = decodedInfo.nodeInfo.user.longName
|
||||
fetchedNode[0].user!.shortName = decodedInfo.nodeInfo.user.shortName
|
||||
fetchedNode[0].user!.macaddr = decodedInfo.nodeInfo.user.macaddr
|
||||
fetchedNode[0].user!.hwModel = String(describing: decodedInfo.nodeInfo.user.hwModel).uppercased()
|
||||
}
|
||||
|
||||
if decodedInfo.nodeInfo.hasDeviceMetrics {
|
||||
|
||||
let newTelemetry = TelemetryEntity(context: context!)
|
||||
|
||||
newTelemetry.batteryLevel = Int32(decodedInfo.nodeInfo.deviceMetrics.batteryLevel)
|
||||
newTelemetry.voltage = decodedInfo.nodeInfo.deviceMetrics.voltage
|
||||
newTelemetry.channelUtilization = decodedInfo.nodeInfo.deviceMetrics.channelUtilization
|
||||
self.connectedPeripheral.channelUtilization = newTelemetry.channelUtilization
|
||||
newTelemetry.airUtilTx = decodedInfo.nodeInfo.deviceMetrics.airUtilTx
|
||||
self.connectedPeripheral.airTime = decodedInfo.nodeInfo.deviceMetrics.airUtilTx
|
||||
|
||||
let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as! NSMutableOrderedSet
|
||||
fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet
|
||||
}
|
||||
|
||||
if decodedInfo.nodeInfo.hasPosition {
|
||||
|
||||
let position = PositionEntity(context: context!)
|
||||
position.latitudeI = decodedInfo.nodeInfo.position.latitudeI
|
||||
position.longitudeI = decodedInfo.nodeInfo.position.longitudeI
|
||||
position.altitude = decodedInfo.nodeInfo.position.altitude
|
||||
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(decodedInfo.nodeInfo.position.time)))
|
||||
|
||||
let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet
|
||||
|
||||
fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
|
||||
|
||||
}
|
||||
|
||||
// Look for a MyInfo
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(decodedInfo.nodeInfo.num))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedMyInfo = try context?.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
|
||||
if fetchedMyInfo.count > 0 {
|
||||
|
||||
fetchedNode[0].myInfo = fetchedMyInfo[0]
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("💥 Fetch MyInfo Error")
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
try context!.save()
|
||||
print("💾 Saved a nodeInfo for \(decodedInfo.nodeInfo.num)")
|
||||
|
||||
} catch {
|
||||
|
||||
context!.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving Core Data NodeInfoEntity: \(nsError)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
print("💥 Fetch NodeInfoEntity Error")
|
||||
}
|
||||
|
||||
if decodedInfo.nodeInfo.hasUser {
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") }
|
||||
|
||||
} else {
|
||||
|
||||
if meshLoggingEnabled { MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.num)") }
|
||||
// MARK: Share Location Position Update Timer
|
||||
// Use context to pass the radio name with the timer
|
||||
// Use a RunLoop to prevent the timer from running on the main UI thread
|
||||
if userSettings?.provideLocation ?? false {
|
||||
|
||||
if self.positionTimer != nil {
|
||||
self.positionTimer!.invalidate()
|
||||
}
|
||||
let context = ["name": "@\(peripheral.name ?? "Unknown")"]
|
||||
self.positionTimer = Timer.scheduledTimer(timeInterval: TimeInterval((userSettings?.provideLocationInterval ?? 900)), target: self, selector: #selector(positionTimerFired), userInfo: context, repeats: true)
|
||||
RunLoop.current.add(self.positionTimer!, forMode: .common)
|
||||
}
|
||||
|
||||
if decodedInfo.configCompleteID != 0 {
|
||||
|
|
@ -720,7 +513,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
case FROMNUM_UUID :
|
||||
|
||||
print("🗞️ FROMNUM Notification, value will be read below")
|
||||
print("🗞️ BLE FROMNUM (Notify) characteristic, value will be read next")
|
||||
|
||||
default:
|
||||
|
||||
|
|
@ -731,7 +524,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
peripheral.readValue(for: FROMRADIO_characteristic)
|
||||
}
|
||||
|
||||
// Send Message
|
||||
public func sendMessage(message: String, toUserNum: Int64, isEmoji: Bool, replyID: Int64) -> Bool {
|
||||
|
||||
var success = false
|
||||
|
|
@ -854,7 +646,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
return success
|
||||
}
|
||||
|
||||
// Send Position
|
||||
public func sendPosition(destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
|
||||
var success = false
|
||||
|
|
@ -884,7 +675,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
|
||||
var meshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(destNum)
|
||||
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = 0 // Send 0 as from from phone to device to avoid warning about client trying to set node num
|
||||
meshPacket.wantAck = wantResponse
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
|
|
@ -933,50 +724,14 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Device Settings
|
||||
public func getSettings() -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = UInt32(connectedPeripheral.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = true
|
||||
meshPacket.hopLimit = 0
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Send Position
|
||||
public func sendShutdown(destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.rebootSeconds = 30
|
||||
adminPacket.shutdownSeconds = 10
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = false
|
||||
|
|
@ -1002,6 +757,41 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
|
|||
}
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
public func sendReboot(destNum: Int64, wantResponse: Bool) -> Bool {
|
||||
|
||||
var adminPacket = AdminMessage()
|
||||
adminPacket.rebootSeconds = 10
|
||||
|
||||
var meshPacket: MeshPacket = MeshPacket()
|
||||
meshPacket.to = UInt32(connectedPeripheral.num)
|
||||
meshPacket.from = 0 //UInt32(connectedPeripheral.num)
|
||||
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
|
||||
meshPacket.priority = MeshPacket.Priority.reliable
|
||||
meshPacket.wantAck = false
|
||||
meshPacket.hopLimit = 0
|
||||
|
||||
var dataMessage = DataMessage()
|
||||
dataMessage.payload = try! adminPacket.serializedData()
|
||||
dataMessage.portnum = PortNum.adminApp
|
||||
|
||||
meshPacket.decoded = dataMessage
|
||||
|
||||
var toRadio: ToRadio!
|
||||
toRadio = ToRadio()
|
||||
toRadio.packet = meshPacket
|
||||
|
||||
let binaryData: Data = try! toRadio.serializedData()
|
||||
|
||||
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
|
||||
|
||||
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
26
MeshtasticClient/Helpers/ChannelHelper.swift
Normal file
26
MeshtasticClient/Helpers/ChannelHelper.swift
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// ChannelHelper.swift
|
||||
// MeshtasticClient
|
||||
//
|
||||
// Created by Garth Vander Houwen on 6/6/22.
|
||||
//
|
||||
|
||||
//import Foundation
|
||||
//
|
||||
//
|
||||
//private fun urlToChannels(url: Uri): AppOnlyProtos.ChannelSet {
|
||||
// val urlStr = url.toString()
|
||||
//
|
||||
// val pathRegex = Regex("$prefix(.*)", RegexOption.IGNORE_CASE)
|
||||
// val (base64) = pathRegex.find(urlStr)?.destructured
|
||||
// ?: throw MalformedURLException("Not a meshtastic URL: ${urlStr.take(40)}")
|
||||
// val bytes = Base64.decode(base64, base64Flags)
|
||||
//
|
||||
// return AppOnlyProtos.ChannelSet.parseFrom(bytes)
|
||||
// }
|
||||
//
|
||||
//func urlToChannels(url: URL) {
|
||||
//
|
||||
// let urlString = url.absoluteString
|
||||
//
|
||||
//}
|
||||
|
|
@ -44,8 +44,6 @@ class LocalNotificationManager {
|
|||
UNUserNotificationCenter.current().add(request) { error in
|
||||
|
||||
guard error == nil else { return }
|
||||
|
||||
print("Notification scheduled! --- ID = \(notification.id)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,266 @@
|
|||
import Foundation
|
||||
import CoreData
|
||||
|
||||
func myInfoPacket (myInfo: MyNodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> MyInfoEntity? {
|
||||
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(myInfo.myNodeNum))
|
||||
|
||||
do {
|
||||
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
|
||||
// Not Found Insert
|
||||
if fetchedMyInfo.isEmpty {
|
||||
|
||||
let myInfoEntity = MyInfoEntity(context: context)
|
||||
myInfoEntity.myNodeNum = Int64(myInfo.myNodeNum)
|
||||
myInfoEntity.hasGps = myInfo.hasGps_p
|
||||
myInfoEntity.hasWifi = myInfo.hasWifi_p
|
||||
myInfoEntity.bitrate = myInfo.bitrate
|
||||
|
||||
func nodeInfoPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) {
|
||||
// Swift does strings weird, this does work to get the version without the github hash
|
||||
let lastDotIndex = myInfo.firmwareVersion.lastIndex(of: ".")
|
||||
var version = myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset: 6, in: myInfo.firmwareVersion))]
|
||||
version = version.dropLast()
|
||||
myInfoEntity.firmwareVersion = String(version)
|
||||
myInfoEntity.messageTimeoutMsec = Int32(bitPattern: myInfo.messageTimeoutMsec)
|
||||
myInfoEntity.minAppVersion = Int32(bitPattern: myInfo.minAppVersion)
|
||||
myInfoEntity.maxChannels = Int32(bitPattern: myInfo.maxChannels)
|
||||
|
||||
do {
|
||||
|
||||
try context.save()
|
||||
if meshLogging { MeshLogger.log("💾 Saved a new myInfo for node number: \(String(myInfo.myNodeNum))") }
|
||||
return myInfoEntity
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Inserting New Core Data MyInfoEntity: \(nsError)")
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
fetchedMyInfo[0].myNodeNum = Int64(myInfo.myNodeNum)
|
||||
fetchedMyInfo[0].hasGps = myInfo.hasGps_p
|
||||
fetchedMyInfo[0].bitrate = myInfo.bitrate
|
||||
|
||||
let lastDotIndex = myInfo.firmwareVersion.lastIndex(of: ".")//.lastIndex(of: ".", offsetBy: -1)
|
||||
var version = myInfo.firmwareVersion[...(lastDotIndex ?? String.Index(utf16Offset:6, in: myInfo.firmwareVersion))]
|
||||
version = version.dropLast()
|
||||
fetchedMyInfo[0].firmwareVersion = String(version)
|
||||
fetchedMyInfo[0].messageTimeoutMsec = Int32(bitPattern: myInfo.messageTimeoutMsec)
|
||||
fetchedMyInfo[0].minAppVersion = Int32(bitPattern: myInfo.minAppVersion)
|
||||
fetchedMyInfo[0].maxChannels = Int32(bitPattern: myInfo.maxChannels)
|
||||
|
||||
do {
|
||||
|
||||
try context.save()
|
||||
if meshLogging { MeshLogger.log("💾 Updated myInfo for node number: \(String(myInfo.myNodeNum))") }
|
||||
return fetchedMyInfo[0]
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Updating Core Data MyInfoEntity: \(nsError)")
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
print("💥 Fetch MyInfo Error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedObjectContext) -> NodeInfoEntity? {
|
||||
|
||||
let fetchNodeInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(nodeInfo.num))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedNode = try context.fetch(fetchNodeInfoRequest) as! [NodeInfoEntity]
|
||||
// Not Found Insert
|
||||
if fetchedNode.isEmpty && nodeInfo.hasUser {
|
||||
|
||||
let newNode = NodeInfoEntity(context: context)
|
||||
newNode.id = Int64(nodeInfo.num)
|
||||
newNode.num = Int64(nodeInfo.num)
|
||||
|
||||
if nodeInfo.hasDeviceMetrics {
|
||||
|
||||
let telemetry = TelemetryEntity(context: context)
|
||||
|
||||
telemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel)
|
||||
telemetry.voltage = nodeInfo.deviceMetrics.voltage
|
||||
telemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization
|
||||
telemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx
|
||||
|
||||
var newTelemetries = [TelemetryEntity]()
|
||||
newTelemetries.append(telemetry)
|
||||
newNode.telemetries? = NSOrderedSet(array: newTelemetries)
|
||||
}
|
||||
|
||||
newNode.lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard)))
|
||||
newNode.snr = nodeInfo.snr
|
||||
|
||||
if nodeInfo.hasUser {
|
||||
|
||||
let newUser = UserEntity(context: context)
|
||||
newUser.userId = nodeInfo.user.id
|
||||
newUser.num = Int64(nodeInfo.num)
|
||||
newUser.longName = nodeInfo.user.longName
|
||||
newUser.shortName = nodeInfo.user.shortName
|
||||
newUser.macaddr = nodeInfo.user.macaddr
|
||||
newUser.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
|
||||
newNode.user = newUser
|
||||
}
|
||||
|
||||
let position = PositionEntity(context: context)
|
||||
position.latitudeI = nodeInfo.position.latitudeI
|
||||
position.longitudeI = nodeInfo.position.longitudeI
|
||||
position.altitude = nodeInfo.position.altitude
|
||||
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time)))
|
||||
|
||||
var newPostions = [PositionEntity]()
|
||||
newPostions.append(position)
|
||||
newNode.positions? = NSOrderedSet(array: newPostions)
|
||||
|
||||
// Look for a MyInfo
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
|
||||
if fetchedMyInfo.count > 0 {
|
||||
newNode.myInfo = fetchedMyInfo[0]
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try context.save()
|
||||
|
||||
if nodeInfo.hasUser {
|
||||
|
||||
if meshLogging { MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.user.longName)") }
|
||||
|
||||
} else {
|
||||
|
||||
if meshLogging { MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.num)") }
|
||||
}
|
||||
return newNode
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving Core Data NodeInfoEntity: \(nsError)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("💥 Fetch MyInfo Error")
|
||||
}
|
||||
|
||||
} else if nodeInfo.hasUser && nodeInfo.num > 0 {
|
||||
|
||||
fetchedNode[0].id = Int64(nodeInfo.num)
|
||||
fetchedNode[0].num = Int64(nodeInfo.num)
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.lastHeard)))
|
||||
fetchedNode[0].snr = nodeInfo.snr
|
||||
|
||||
|
||||
if nodeInfo.hasUser {
|
||||
|
||||
fetchedNode[0].user!.userId = nodeInfo.user.id
|
||||
fetchedNode[0].user!.num = Int64(nodeInfo.num)
|
||||
fetchedNode[0].user!.longName = nodeInfo.user.longName
|
||||
fetchedNode[0].user!.shortName = nodeInfo.user.shortName
|
||||
fetchedNode[0].user!.macaddr = nodeInfo.user.macaddr
|
||||
fetchedNode[0].user!.hwModel = String(describing: nodeInfo.user.hwModel).uppercased()
|
||||
}
|
||||
|
||||
if nodeInfo.hasDeviceMetrics {
|
||||
|
||||
let newTelemetry = TelemetryEntity(context: context)
|
||||
|
||||
newTelemetry.batteryLevel = Int32(nodeInfo.deviceMetrics.batteryLevel)
|
||||
newTelemetry.voltage = nodeInfo.deviceMetrics.voltage
|
||||
newTelemetry.channelUtilization = nodeInfo.deviceMetrics.channelUtilization
|
||||
newTelemetry.airUtilTx = nodeInfo.deviceMetrics.airUtilTx
|
||||
|
||||
let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as! NSMutableOrderedSet
|
||||
fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet
|
||||
}
|
||||
|
||||
if nodeInfo.hasPosition {
|
||||
|
||||
let position = PositionEntity(context: context)
|
||||
position.latitudeI = nodeInfo.position.latitudeI
|
||||
position.longitudeI = nodeInfo.position.longitudeI
|
||||
position.altitude = nodeInfo.position.altitude
|
||||
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(nodeInfo.position.time)))
|
||||
|
||||
let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet
|
||||
|
||||
fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
|
||||
|
||||
}
|
||||
|
||||
// Look for a MyInfo
|
||||
let fetchMyInfoRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MyInfoEntity")
|
||||
fetchMyInfoRequest.predicate = NSPredicate(format: "myNodeNum == %lld", Int64(nodeInfo.num))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedMyInfo = try context.fetch(fetchMyInfoRequest) as! [MyInfoEntity]
|
||||
if fetchedMyInfo.count > 0 {
|
||||
|
||||
fetchedNode[0].myInfo = fetchedMyInfo[0]
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try context.save()
|
||||
|
||||
if nodeInfo.hasUser {
|
||||
|
||||
if meshLogging { MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.user.longName)") }
|
||||
|
||||
} else {
|
||||
|
||||
if meshLogging { MeshLogger.log("💾 BLE FROMRADIO received and nodeInfo inserted for \(nodeInfo.num)") }
|
||||
}
|
||||
|
||||
return fetchedNode[0]
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving Core Data NodeInfoEntity: \(nsError)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("💥 Fetch MyInfo Error")
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
print("💥 Fetch NodeInfoEntity Error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func nodeInfoAppPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) {
|
||||
|
||||
let fetchNodeInfoAppRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeInfoAppRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
|
||||
|
|
@ -24,7 +281,24 @@ func nodeInfoPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb
|
|||
fetchedNode[0].num = Int64(packet.from)
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
|
||||
fetchedNode[0].snr = packet.rxSnr
|
||||
|
||||
|
||||
if let nodeInfoMessage = try? NodeInfo(serializedData: packet.decoded.payload) {
|
||||
|
||||
if nodeInfoMessage.hasDeviceMetrics {
|
||||
|
||||
let telemetry = TelemetryEntity(context: context)
|
||||
|
||||
telemetry.batteryLevel = Int32(nodeInfoMessage.deviceMetrics.batteryLevel)
|
||||
telemetry.voltage = nodeInfoMessage.deviceMetrics.voltage
|
||||
telemetry.channelUtilization = nodeInfoMessage.deviceMetrics.channelUtilization
|
||||
telemetry.airUtilTx = nodeInfoMessage.deviceMetrics.airUtilTx
|
||||
|
||||
var newTelemetries = [TelemetryEntity]()
|
||||
newTelemetries.append(telemetry)
|
||||
fetchedNode[0].telemetries? = NSOrderedSet(array: newTelemetries)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
|
@ -107,8 +381,6 @@ func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb
|
|||
func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) {
|
||||
|
||||
if let routingMessage = try? Routing(serializedData: packet.decoded.payload) {
|
||||
print(packet.decoded.requestID)
|
||||
print(routingMessage)
|
||||
|
||||
let error = routingMessage.errorReason
|
||||
|
||||
|
|
@ -138,16 +410,13 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj
|
|||
case Routing.Error.notAuthorized:
|
||||
errorExplanation = "The application layer service on the remote node received your request, but considered your request not authorized (i.e you did not send the request on the required bound channel)"
|
||||
fallthrough
|
||||
default:
|
||||
print(error)
|
||||
default: ()
|
||||
}
|
||||
|
||||
if meshLogging { MeshLogger.log("🕸️ ROUTING PACKET received for RequestID: \(packet.decoded.requestID) Error: \(errorExplanation)") }
|
||||
|
||||
if routingMessage.errorReason == Routing.Error.none {
|
||||
|
||||
print("Priority ACK no Error")
|
||||
|
||||
let fetchMessageRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "MessageEntity")
|
||||
fetchMessageRequest.predicate = NSPredicate(format: "messageId == %lld", Int64(packet.decoded.requestID))
|
||||
|
||||
|
|
@ -161,6 +430,8 @@ func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObj
|
|||
fetchedMessage!.ackSNR = packet.rxSnr
|
||||
fetchedMessage!.ackTimestamp = Int32(packet.rxTime)
|
||||
fetchedMessage!.objectWillChange.send()
|
||||
fetchedMessage!.fromUser?.objectWillChange.send()
|
||||
fetchedMessage!.toUser?.objectWillChange.send()
|
||||
}
|
||||
|
||||
try context.save()
|
||||
|
|
@ -186,7 +457,48 @@ func telemetryPacket(packet: MeshPacket, meshLogging: Bool, context: NSManagedOb
|
|||
|
||||
let telemetry = TelemetryEntity(context: context)
|
||||
|
||||
if meshLogging { MeshLogger.log("ℹ️ MESH PACKET received for Telemetry App UNHANDLED \(telemetryMessage)") }
|
||||
let fetchNodeTelemetryRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
|
||||
fetchNodeTelemetryRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
|
||||
|
||||
do {
|
||||
|
||||
let fetchedNode = try context.fetch(fetchNodeTelemetryRequest) as! [NodeInfoEntity]
|
||||
|
||||
if fetchedNode.count == 1 {
|
||||
|
||||
// Device Metrics
|
||||
telemetry.airUtilTx = telemetryMessage.deviceMetrics.airUtilTx
|
||||
telemetry.channelUtilization = telemetryMessage.deviceMetrics.channelUtilization
|
||||
telemetry.batteryLevel = Int32(telemetryMessage.deviceMetrics.batteryLevel)
|
||||
telemetry.voltage = telemetryMessage.deviceMetrics.voltage
|
||||
|
||||
// Environment Metrics
|
||||
telemetry.barometricPressure = telemetryMessage.environmentMetrics.barometricPressure
|
||||
telemetry.current = telemetryMessage.environmentMetrics.current
|
||||
telemetry.gasResistance = telemetryMessage.environmentMetrics.gasResistance
|
||||
telemetry.relativeHumidity = telemetryMessage.environmentMetrics.relativeHumidity
|
||||
telemetry.temperature = telemetryMessage.environmentMetrics.temperature
|
||||
let mutableTelemetries = fetchedNode[0].telemetries!.mutableCopy() as! NSMutableOrderedSet
|
||||
mutableTelemetries.add(telemetry)
|
||||
|
||||
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(telemetryMessage.time)))
|
||||
fetchedNode[0].telemetries = mutableTelemetries.copy() as? NSOrderedSet
|
||||
fetchedNode[0].objectWillChange.send()
|
||||
}
|
||||
|
||||
try context.save()
|
||||
|
||||
if meshLogging {
|
||||
MeshLogger.log("💾 Telemetry Saved for Node: \(packet.from)")
|
||||
}
|
||||
|
||||
} catch {
|
||||
|
||||
context.rollback()
|
||||
|
||||
let nsError = error as NSError
|
||||
print("💥 Error Saving Telemetry for Node \(packet.from) Error: \(nsError)")
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
|
@ -238,6 +550,8 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, meshLogging:
|
|||
|
||||
newMessage.fromUser = fetchedUsers.first(where: { $0.num == packet.from })
|
||||
newMessage.messagePayload = messageText
|
||||
newMessage.fromUser?.objectWillChange.send()
|
||||
newMessage.toUser?.objectWillChange.send()
|
||||
|
||||
do {
|
||||
|
||||
|
|
|
|||
16
MeshtasticClient/Persistence/UserEntityExtension.swift
Normal file
16
MeshtasticClient/Persistence/UserEntityExtension.swift
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// UserEntityExtension.swift
|
||||
// MeshtasticClient
|
||||
//
|
||||
// Created by Garth Vander Houwen on 6/3/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension UserEntity {
|
||||
|
||||
var messageList: [MessageEntity] {
|
||||
|
||||
self.value(forKey: "allMessages") as! [MessageEntity]
|
||||
}
|
||||
}
|
||||
|
|
@ -32,12 +32,25 @@ struct ChannelSet {
|
|||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
/// Channel list with settings
|
||||
var settings: [ChannelSettings] = []
|
||||
|
||||
///
|
||||
/// LoRa config
|
||||
var loraConfig: Config.LoRaConfig {
|
||||
get {return _loraConfig ?? Config.LoRaConfig()}
|
||||
set {_loraConfig = newValue}
|
||||
}
|
||||
/// Returns true if `loraConfig` has been explicitly set.
|
||||
var hasLoraConfig: Bool {return self._loraConfig != nil}
|
||||
/// Clears the value of `loraConfig`. Subsequent reads from it will return its default value.
|
||||
mutating func clearLoraConfig() {self._loraConfig = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _loraConfig: Config.LoRaConfig? = nil
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
|
|
@ -50,6 +63,7 @@ extension ChannelSet: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
static let protoMessageName: String = "ChannelSet"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "settings"),
|
||||
2: .standard(proto: "lora_config"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
|
|
@ -59,20 +73,29 @@ extension ChannelSet: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementatio
|
|||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeRepeatedMessageField(value: &self.settings) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &self._loraConfig) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if !self.settings.isEmpty {
|
||||
try visitor.visitRepeatedMessageField(value: self.settings, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = self._loraConfig {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: ChannelSet, rhs: ChannelSet) -> Bool {
|
||||
if lhs.settings != rhs.settings {return false}
|
||||
if lhs._loraConfig != rhs._loraConfig {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,20 +283,6 @@ struct Config {
|
|||
/// window.
|
||||
var gpsAttemptTime: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Shall we accept 2D GPS fixes? By default, only 3D fixes are accepted
|
||||
/// (during a 2D fix, altitude values are unreliable and will be excluded)
|
||||
var gpsAccept2D: Bool = false
|
||||
|
||||
///
|
||||
/// GPS maximum DOP accepted (dilution of precision)
|
||||
/// Set a rejection threshold for GPS readings based on their precision,
|
||||
/// relative to the GPS rated accuracy (which is typically ~3m)
|
||||
/// Solutions above this value will be treated as retryable errors!
|
||||
/// Useful range is between 1 - 64 (3m - <~200m)
|
||||
/// By default (if zero), accept all GPS readings
|
||||
var gpsMaxDop: UInt32 = 0
|
||||
|
||||
///
|
||||
/// Bit field of boolean configuration options for POSITION messages
|
||||
/// (bitwise OR of PositionFlags)
|
||||
|
|
@ -1294,8 +1280,6 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
5: .standard(proto: "gps_disabled"),
|
||||
6: .standard(proto: "gps_update_interval"),
|
||||
7: .standard(proto: "gps_attempt_time"),
|
||||
8: .standard(proto: "gps_accept_2d"),
|
||||
9: .standard(proto: "gps_max_dop"),
|
||||
10: .standard(proto: "position_flags"),
|
||||
]
|
||||
|
||||
|
|
@ -1311,8 +1295,6 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
case 5: try { try decoder.decodeSingularBoolField(value: &self.gpsDisabled) }()
|
||||
case 6: try { try decoder.decodeSingularUInt32Field(value: &self.gpsUpdateInterval) }()
|
||||
case 7: try { try decoder.decodeSingularUInt32Field(value: &self.gpsAttemptTime) }()
|
||||
case 8: try { try decoder.decodeSingularBoolField(value: &self.gpsAccept2D) }()
|
||||
case 9: try { try decoder.decodeSingularUInt32Field(value: &self.gpsMaxDop) }()
|
||||
case 10: try { try decoder.decodeSingularUInt32Field(value: &self.positionFlags) }()
|
||||
default: break
|
||||
}
|
||||
|
|
@ -1338,12 +1320,6 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
if self.gpsAttemptTime != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.gpsAttemptTime, fieldNumber: 7)
|
||||
}
|
||||
if self.gpsAccept2D != false {
|
||||
try visitor.visitSingularBoolField(value: self.gpsAccept2D, fieldNumber: 8)
|
||||
}
|
||||
if self.gpsMaxDop != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.gpsMaxDop, fieldNumber: 9)
|
||||
}
|
||||
if self.positionFlags != 0 {
|
||||
try visitor.visitSingularUInt32Field(value: self.positionFlags, fieldNumber: 10)
|
||||
}
|
||||
|
|
@ -1357,8 +1333,6 @@ extension Config.PositionConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
|
|||
if lhs.gpsDisabled != rhs.gpsDisabled {return false}
|
||||
if lhs.gpsUpdateInterval != rhs.gpsUpdateInterval {return false}
|
||||
if lhs.gpsAttemptTime != rhs.gpsAttemptTime {return false}
|
||||
if lhs.gpsAccept2D != rhs.gpsAccept2D {return false}
|
||||
if lhs.gpsMaxDop != rhs.gpsMaxDop {return false}
|
||||
if lhs.positionFlags != rhs.positionFlags {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -215,180 +215,11 @@ struct OEMStore {
|
|||
init() {}
|
||||
}
|
||||
|
||||
struct LocalConfig {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var device: Config.DeviceConfig {
|
||||
get {return _storage._device ?? Config.DeviceConfig()}
|
||||
set {_uniqueStorage()._device = newValue}
|
||||
}
|
||||
/// Returns true if `device` has been explicitly set.
|
||||
var hasDevice: Bool {return _storage._device != nil}
|
||||
/// Clears the value of `device`. Subsequent reads from it will return its default value.
|
||||
mutating func clearDevice() {_uniqueStorage()._device = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var position: Config.PositionConfig {
|
||||
get {return _storage._position ?? Config.PositionConfig()}
|
||||
set {_uniqueStorage()._position = newValue}
|
||||
}
|
||||
/// Returns true if `position` has been explicitly set.
|
||||
var hasPosition: Bool {return _storage._position != nil}
|
||||
/// Clears the value of `position`. Subsequent reads from it will return its default value.
|
||||
mutating func clearPosition() {_uniqueStorage()._position = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var power: Config.PowerConfig {
|
||||
get {return _storage._power ?? Config.PowerConfig()}
|
||||
set {_uniqueStorage()._power = newValue}
|
||||
}
|
||||
/// Returns true if `power` has been explicitly set.
|
||||
var hasPower: Bool {return _storage._power != nil}
|
||||
/// Clears the value of `power`. Subsequent reads from it will return its default value.
|
||||
mutating func clearPower() {_uniqueStorage()._power = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var wifi: Config.WiFiConfig {
|
||||
get {return _storage._wifi ?? Config.WiFiConfig()}
|
||||
set {_uniqueStorage()._wifi = newValue}
|
||||
}
|
||||
/// Returns true if `wifi` has been explicitly set.
|
||||
var hasWifi: Bool {return _storage._wifi != nil}
|
||||
/// Clears the value of `wifi`. Subsequent reads from it will return its default value.
|
||||
mutating func clearWifi() {_uniqueStorage()._wifi = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var display: Config.DisplayConfig {
|
||||
get {return _storage._display ?? Config.DisplayConfig()}
|
||||
set {_uniqueStorage()._display = newValue}
|
||||
}
|
||||
/// Returns true if `display` has been explicitly set.
|
||||
var hasDisplay: Bool {return _storage._display != nil}
|
||||
/// Clears the value of `display`. Subsequent reads from it will return its default value.
|
||||
mutating func clearDisplay() {_uniqueStorage()._display = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var lora: Config.LoRaConfig {
|
||||
get {return _storage._lora ?? Config.LoRaConfig()}
|
||||
set {_uniqueStorage()._lora = newValue}
|
||||
}
|
||||
/// Returns true if `lora` has been explicitly set.
|
||||
var hasLora: Bool {return _storage._lora != nil}
|
||||
/// Clears the value of `lora`. Subsequent reads from it will return its default value.
|
||||
mutating func clearLora() {_uniqueStorage()._lora = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _storage = _StorageClass.defaultInstance
|
||||
}
|
||||
|
||||
struct LocalModuleConfig {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var mqtt: ModuleConfig.MQTTConfig {
|
||||
get {return _storage._mqtt ?? ModuleConfig.MQTTConfig()}
|
||||
set {_uniqueStorage()._mqtt = newValue}
|
||||
}
|
||||
/// Returns true if `mqtt` has been explicitly set.
|
||||
var hasMqtt: Bool {return _storage._mqtt != nil}
|
||||
/// Clears the value of `mqtt`. Subsequent reads from it will return its default value.
|
||||
mutating func clearMqtt() {_uniqueStorage()._mqtt = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var serial: ModuleConfig.SerialConfig {
|
||||
get {return _storage._serial ?? ModuleConfig.SerialConfig()}
|
||||
set {_uniqueStorage()._serial = newValue}
|
||||
}
|
||||
/// Returns true if `serial` has been explicitly set.
|
||||
var hasSerial: Bool {return _storage._serial != nil}
|
||||
/// Clears the value of `serial`. Subsequent reads from it will return its default value.
|
||||
mutating func clearSerial() {_uniqueStorage()._serial = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var externalNotification: ModuleConfig.ExternalNotificationConfig {
|
||||
get {return _storage._externalNotification ?? ModuleConfig.ExternalNotificationConfig()}
|
||||
set {_uniqueStorage()._externalNotification = newValue}
|
||||
}
|
||||
/// Returns true if `externalNotification` has been explicitly set.
|
||||
var hasExternalNotification: Bool {return _storage._externalNotification != nil}
|
||||
/// Clears the value of `externalNotification`. Subsequent reads from it will return its default value.
|
||||
mutating func clearExternalNotification() {_uniqueStorage()._externalNotification = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var storeForward: ModuleConfig.StoreForwardConfig {
|
||||
get {return _storage._storeForward ?? ModuleConfig.StoreForwardConfig()}
|
||||
set {_uniqueStorage()._storeForward = newValue}
|
||||
}
|
||||
/// Returns true if `storeForward` has been explicitly set.
|
||||
var hasStoreForward: Bool {return _storage._storeForward != nil}
|
||||
/// Clears the value of `storeForward`. Subsequent reads from it will return its default value.
|
||||
mutating func clearStoreForward() {_uniqueStorage()._storeForward = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var rangeTest: ModuleConfig.RangeTestConfig {
|
||||
get {return _storage._rangeTest ?? ModuleConfig.RangeTestConfig()}
|
||||
set {_uniqueStorage()._rangeTest = newValue}
|
||||
}
|
||||
/// Returns true if `rangeTest` has been explicitly set.
|
||||
var hasRangeTest: Bool {return _storage._rangeTest != nil}
|
||||
/// Clears the value of `rangeTest`. Subsequent reads from it will return its default value.
|
||||
mutating func clearRangeTest() {_uniqueStorage()._rangeTest = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var telemetry: ModuleConfig.TelemetryConfig {
|
||||
get {return _storage._telemetry ?? ModuleConfig.TelemetryConfig()}
|
||||
set {_uniqueStorage()._telemetry = newValue}
|
||||
}
|
||||
/// Returns true if `telemetry` has been explicitly set.
|
||||
var hasTelemetry: Bool {return _storage._telemetry != nil}
|
||||
/// Clears the value of `telemetry`. Subsequent reads from it will return its default value.
|
||||
mutating func clearTelemetry() {_uniqueStorage()._telemetry = nil}
|
||||
|
||||
///
|
||||
/// TODO: REPLACE
|
||||
var cannedMessage: ModuleConfig.CannedMessageConfig {
|
||||
get {return _storage._cannedMessage ?? ModuleConfig.CannedMessageConfig()}
|
||||
set {_uniqueStorage()._cannedMessage = newValue}
|
||||
}
|
||||
/// Returns true if `cannedMessage` has been explicitly set.
|
||||
var hasCannedMessage: Bool {return _storage._cannedMessage != nil}
|
||||
/// Clears the value of `cannedMessage`. Subsequent reads from it will return its default value.
|
||||
mutating func clearCannedMessage() {_uniqueStorage()._cannedMessage = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _storage = _StorageClass.defaultInstance
|
||||
}
|
||||
|
||||
#if swift(>=5.5) && canImport(_Concurrency)
|
||||
extension ScreenFonts: @unchecked Sendable {}
|
||||
extension DeviceState: @unchecked Sendable {}
|
||||
extension ChannelFile: @unchecked Sendable {}
|
||||
extension OEMStore: @unchecked Sendable {}
|
||||
extension LocalConfig: @unchecked Sendable {}
|
||||
extension LocalModuleConfig: @unchecked Sendable {}
|
||||
#endif // swift(>=5.5) && canImport(_Concurrency)
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
|
@ -612,227 +443,3 @@ extension OEMStore: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationB
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "LocalConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "device"),
|
||||
2: .same(proto: "position"),
|
||||
3: .same(proto: "power"),
|
||||
4: .same(proto: "wifi"),
|
||||
5: .same(proto: "display"),
|
||||
6: .same(proto: "lora"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
var _device: Config.DeviceConfig? = nil
|
||||
var _position: Config.PositionConfig? = nil
|
||||
var _power: Config.PowerConfig? = nil
|
||||
var _wifi: Config.WiFiConfig? = nil
|
||||
var _display: Config.DisplayConfig? = nil
|
||||
var _lora: Config.LoRaConfig? = nil
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
private init() {}
|
||||
|
||||
init(copying source: _StorageClass) {
|
||||
_device = source._device
|
||||
_position = source._position
|
||||
_power = source._power
|
||||
_wifi = source._wifi
|
||||
_display = source._display
|
||||
_lora = source._lora
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate mutating func _uniqueStorage() -> _StorageClass {
|
||||
if !isKnownUniquelyReferenced(&_storage) {
|
||||
_storage = _StorageClass(copying: _storage)
|
||||
}
|
||||
return _storage
|
||||
}
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
_ = _uniqueStorage()
|
||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularMessageField(value: &_storage._device) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &_storage._position) }()
|
||||
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._power) }()
|
||||
case 4: try { try decoder.decodeSingularMessageField(value: &_storage._wifi) }()
|
||||
case 5: try { try decoder.decodeSingularMessageField(value: &_storage._display) }()
|
||||
case 6: try { try decoder.decodeSingularMessageField(value: &_storage._lora) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
try { if let v = _storage._device {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
|
||||
} }()
|
||||
try { if let v = _storage._position {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = _storage._power {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
try { if let v = _storage._wifi {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 4)
|
||||
} }()
|
||||
try { if let v = _storage._display {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 5)
|
||||
} }()
|
||||
try { if let v = _storage._lora {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||
} }()
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: LocalConfig, rhs: LocalConfig) -> Bool {
|
||||
if lhs._storage !== rhs._storage {
|
||||
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
|
||||
let _storage = _args.0
|
||||
let rhs_storage = _args.1
|
||||
if _storage._device != rhs_storage._device {return false}
|
||||
if _storage._position != rhs_storage._position {return false}
|
||||
if _storage._power != rhs_storage._power {return false}
|
||||
if _storage._wifi != rhs_storage._wifi {return false}
|
||||
if _storage._display != rhs_storage._display {return false}
|
||||
if _storage._lora != rhs_storage._lora {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalModuleConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "LocalModuleConfig"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "mqtt"),
|
||||
2: .same(proto: "serial"),
|
||||
3: .standard(proto: "external_notification"),
|
||||
4: .standard(proto: "store_forward"),
|
||||
5: .standard(proto: "range_test"),
|
||||
6: .same(proto: "telemetry"),
|
||||
7: .standard(proto: "canned_message"),
|
||||
]
|
||||
|
||||
fileprivate class _StorageClass {
|
||||
var _mqtt: ModuleConfig.MQTTConfig? = nil
|
||||
var _serial: ModuleConfig.SerialConfig? = nil
|
||||
var _externalNotification: ModuleConfig.ExternalNotificationConfig? = nil
|
||||
var _storeForward: ModuleConfig.StoreForwardConfig? = nil
|
||||
var _rangeTest: ModuleConfig.RangeTestConfig? = nil
|
||||
var _telemetry: ModuleConfig.TelemetryConfig? = nil
|
||||
var _cannedMessage: ModuleConfig.CannedMessageConfig? = nil
|
||||
|
||||
static let defaultInstance = _StorageClass()
|
||||
|
||||
private init() {}
|
||||
|
||||
init(copying source: _StorageClass) {
|
||||
_mqtt = source._mqtt
|
||||
_serial = source._serial
|
||||
_externalNotification = source._externalNotification
|
||||
_storeForward = source._storeForward
|
||||
_rangeTest = source._rangeTest
|
||||
_telemetry = source._telemetry
|
||||
_cannedMessage = source._cannedMessage
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate mutating func _uniqueStorage() -> _StorageClass {
|
||||
if !isKnownUniquelyReferenced(&_storage) {
|
||||
_storage = _StorageClass(copying: _storage)
|
||||
}
|
||||
return _storage
|
||||
}
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
_ = _uniqueStorage()
|
||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularMessageField(value: &_storage._mqtt) }()
|
||||
case 2: try { try decoder.decodeSingularMessageField(value: &_storage._serial) }()
|
||||
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._externalNotification) }()
|
||||
case 4: try { try decoder.decodeSingularMessageField(value: &_storage._storeForward) }()
|
||||
case 5: try { try decoder.decodeSingularMessageField(value: &_storage._rangeTest) }()
|
||||
case 6: try { try decoder.decodeSingularMessageField(value: &_storage._telemetry) }()
|
||||
case 7: try { try decoder.decodeSingularMessageField(value: &_storage._cannedMessage) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
try withExtendedLifetime(_storage) { (_storage: _StorageClass) in
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
try { if let v = _storage._mqtt {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
|
||||
} }()
|
||||
try { if let v = _storage._serial {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = _storage._externalNotification {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
try { if let v = _storage._storeForward {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 4)
|
||||
} }()
|
||||
try { if let v = _storage._rangeTest {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 5)
|
||||
} }()
|
||||
try { if let v = _storage._telemetry {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
|
||||
} }()
|
||||
try { if let v = _storage._cannedMessage {
|
||||
try visitor.visitSingularMessageField(value: v, fieldNumber: 7)
|
||||
} }()
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: LocalModuleConfig, rhs: LocalModuleConfig) -> Bool {
|
||||
if lhs._storage !== rhs._storage {
|
||||
let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in
|
||||
let _storage = _args.0
|
||||
let rhs_storage = _args.1
|
||||
if _storage._mqtt != rhs_storage._mqtt {return false}
|
||||
if _storage._serial != rhs_storage._serial {return false}
|
||||
if _storage._externalNotification != rhs_storage._externalNotification {return false}
|
||||
if _storage._storeForward != rhs_storage._storeForward {return false}
|
||||
if _storage._rangeTest != rhs_storage._rangeTest {return false}
|
||||
if _storage._telemetry != rhs_storage._telemetry {return false}
|
||||
if _storage._cannedMessage != rhs_storage._cannedMessage {return false}
|
||||
return true
|
||||
}
|
||||
if !storagesAreEqual {return false}
|
||||
}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,10 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
/// Compressed TEXT_MESSAGE payloads.
|
||||
case textMessageCompressedApp // = 7
|
||||
|
||||
///
|
||||
/// Waypoint payloads.
|
||||
case waypointApp // = 8
|
||||
|
||||
///
|
||||
/// Provides a 'ping' service that replies to any packet it receives.
|
||||
/// Also serves as a small example module.
|
||||
|
|
@ -143,6 +147,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
case 5: self = .routingApp
|
||||
case 6: self = .adminApp
|
||||
case 7: self = .textMessageCompressedApp
|
||||
case 8: self = .waypointApp
|
||||
case 32: self = .replyApp
|
||||
case 33: self = .ipTunnelApp
|
||||
case 64: self = .serialApp
|
||||
|
|
@ -167,6 +172,7 @@ enum PortNum: SwiftProtobuf.Enum {
|
|||
case .routingApp: return 5
|
||||
case .adminApp: return 6
|
||||
case .textMessageCompressedApp: return 7
|
||||
case .waypointApp: return 8
|
||||
case .replyApp: return 32
|
||||
case .ipTunnelApp: return 33
|
||||
case .serialApp: return 64
|
||||
|
|
@ -196,6 +202,7 @@ extension PortNum: CaseIterable {
|
|||
.routingApp,
|
||||
.adminApp,
|
||||
.textMessageCompressedApp,
|
||||
.waypointApp,
|
||||
.replyApp,
|
||||
.ipTunnelApp,
|
||||
.serialApp,
|
||||
|
|
@ -227,6 +234,7 @@ extension PortNum: SwiftProtobuf._ProtoNameProviding {
|
|||
5: .same(proto: "ROUTING_APP"),
|
||||
6: .same(proto: "ADMIN_APP"),
|
||||
7: .same(proto: "TEXT_MESSAGE_COMPRESSED_APP"),
|
||||
8: .same(proto: "WAYPOINT_APP"),
|
||||
32: .same(proto: "REPLY_APP"),
|
||||
33: .same(proto: "IP_TUNNEL_APP"),
|
||||
64: .same(proto: "SERIAL_APP"),
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ struct Connect: View {
|
|||
var body: some View {
|
||||
|
||||
let firmwareVersion = bleManager.lastConnnectionVersion
|
||||
let minimumVersion = "1.3.0"
|
||||
let minimumVersion = "1.3.15"
|
||||
let supportedVersion = firmwareVersion == "0.0.0" || minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedSame
|
||||
|
||||
NavigationView {
|
||||
|
|
|
|||
30
MeshtasticClient/Views/Helpers/DateTimeText.swift
Normal file
30
MeshtasticClient/Views/Helpers/DateTimeText.swift
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// DateTimeText.swift
|
||||
// MeshtasticClient
|
||||
//
|
||||
// Created by Garth Vander Houwen on 5/30/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
//
|
||||
// LastHeardText.swift
|
||||
// Meshtastic Apple
|
||||
//
|
||||
// Created by Garth Vander Houwen on 5/25/22.
|
||||
//
|
||||
struct DateTimeText: View {
|
||||
var dateTime: Date?
|
||||
|
||||
let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())
|
||||
|
||||
var body: some View {
|
||||
if (dateTime != nil && dateTime! >= sixMonthsAgo!){
|
||||
|
||||
Text("\(dateTime!, style: .date) \(dateTime!, style: .time)")
|
||||
|
||||
} else {
|
||||
|
||||
Text("Unknown Age")
|
||||
}
|
||||
}
|
||||
}
|
||||
26
MeshtasticClient/Views/Helpers/NodeAnnotation.swift
Normal file
26
MeshtasticClient/Views/Helpers/NodeAnnotation.swift
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct NodeAnnotation: View {
|
||||
let time: Date
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text(time, style: .offset)
|
||||
.font(.callout).foregroundColor(.accentColor)
|
||||
.padding(5)
|
||||
.background(Color(.white))
|
||||
.cornerRadius(10)
|
||||
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.font(.largeTitle)
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
Image(systemName: "arrowtriangle.down.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.accentColor)
|
||||
.offset(x: 0, y: -5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +31,8 @@ struct UserMessageList: View {
|
|||
@State private var replyMessageId: Int64 = 0
|
||||
@State private var sendPositionWithMessage: Bool = false
|
||||
|
||||
@State var messageCount = 0
|
||||
@State private var messageCount = 0
|
||||
@State private var refreshId = UUID()
|
||||
|
||||
var body: some View {
|
||||
|
||||
|
|
@ -40,20 +41,14 @@ struct UserMessageList: View {
|
|||
let hasTapbackSupport = minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedAscending || minimumVersion.compare(firmwareVersion, options: .numeric) == .orderedSame
|
||||
|
||||
VStack {
|
||||
|
||||
let allMessages = user.value(forKey: "allMessages") as! [MessageEntity]
|
||||
|
||||
ScrollViewReader { scrollView in
|
||||
|
||||
ScrollView {
|
||||
|
||||
if allMessages.count > 0 {
|
||||
|
||||
HStack{
|
||||
// Padding at the top of the message list
|
||||
}.padding(.bottom)
|
||||
|
||||
ForEach( allMessages ) { (message: MessageEntity) in
|
||||
if user.messageList.count > 0 {
|
||||
|
||||
ForEach( user.messageList ) { (message: MessageEntity) in
|
||||
|
||||
let currentUser: Bool = (bleManager.connectedPeripheral == nil) ? false : ((bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.num == message.fromUser?.num) ? true : false )
|
||||
|
||||
|
|
@ -61,7 +56,7 @@ struct UserMessageList: View {
|
|||
|
||||
if message.replyID > 0 {
|
||||
|
||||
let messageReply = allMessages.first(where: { $0.messageId == message.replyID })
|
||||
let messageReply = user.messageList.first(where: { $0.messageId == message.replyID })
|
||||
|
||||
HStack {
|
||||
|
||||
|
|
@ -299,12 +294,13 @@ struct UserMessageList: View {
|
|||
|
||||
}
|
||||
.padding(.bottom)
|
||||
.id(allMessages.firstIndex(of: message))
|
||||
.id(user.messageList.firstIndex(of: message))
|
||||
|
||||
if !currentUser {
|
||||
Spacer(minLength:50)
|
||||
}
|
||||
}
|
||||
.id(message.messageId)
|
||||
.padding([.leading, .trailing])
|
||||
.frame(maxWidth: .infinity)
|
||||
.alert(isPresented: $showDeleteMessageAlert) {
|
||||
|
|
@ -313,7 +309,7 @@ struct UserMessageList: View {
|
|||
print("OK button tapped")
|
||||
if deleteMessageId > 0 {
|
||||
|
||||
let message = allMessages.first(where: { $0.messageId == deleteMessageId })
|
||||
let message = user.messageList.first(where: { $0.messageId == deleteMessageId })
|
||||
|
||||
context.delete(message!)
|
||||
do {
|
||||
|
|
@ -338,19 +334,19 @@ struct UserMessageList: View {
|
|||
|
||||
self.bleManager.context = context
|
||||
self.bleManager.userSettings = userSettings
|
||||
|
||||
if allMessages.count > 1 {
|
||||
|
||||
scrollView.scrollTo(allMessages.firstIndex(of: allMessages.last! ), anchor: .bottom)
|
||||
}
|
||||
|
||||
messageCount = user.messageList.count
|
||||
refreshId = UUID()
|
||||
|
||||
})
|
||||
.onChange(of: allMessages.count, perform: { count in
|
||||
.onChange(of: messageCount, perform: { value in
|
||||
//scrollView.scrollTo(user.messageList.firstIndex(of: user.messageList.last! ), anchor: .bottom)
|
||||
scrollView.scrollTo(user.messageList.last!.messageId)
|
||||
})
|
||||
.onChange(of: user.messageList, perform: { messages in
|
||||
|
||||
if count > 1 {
|
||||
|
||||
scrollView.scrollTo(allMessages.firstIndex(of: allMessages.last! ), anchor: .bottom)
|
||||
|
||||
}
|
||||
refreshId = UUID()
|
||||
messageCount = messages.count
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ struct NodeDetail: View {
|
|||
@Environment(\.managedObjectContext) var context
|
||||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var userSettings: UserSettings
|
||||
|
||||
@State private var isPresentingShutdownConfirm: Bool = false
|
||||
@State private var isPresentingRebootConfirm: Bool = false
|
||||
|
||||
var node: NodeInfoEntity
|
||||
|
||||
|
|
@ -56,7 +59,7 @@ struct NodeDetail: View {
|
|||
|
||||
content: {
|
||||
|
||||
CircleText(text: node.user!.shortName ?? "???", color: .accentColor)
|
||||
NodeAnnotation(time: location.time!)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -80,8 +83,66 @@ struct NodeDetail: View {
|
|||
}
|
||||
|
||||
ScrollView {
|
||||
|
||||
HStack {
|
||||
if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num {
|
||||
|
||||
Button(action: {
|
||||
|
||||
isPresentingShutdownConfirm = true
|
||||
}) {
|
||||
|
||||
Image(systemName: "power")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.imageScale(.small)
|
||||
.foregroundColor(Color.accentColor)
|
||||
Text("Power Off")
|
||||
.font(.caption)
|
||||
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.clipShape(Capsule())
|
||||
.confirmationDialog(
|
||||
"Are you sure?",
|
||||
isPresented: $isPresentingShutdownConfirm
|
||||
) {
|
||||
Button("Shutdown Node?", role: .destructive) {
|
||||
let success = bleManager.sendShutdown(destNum: node.num, wantResponse: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Button(action: {
|
||||
|
||||
isPresentingRebootConfirm = true
|
||||
|
||||
}) {
|
||||
|
||||
Image(systemName: "arrow.triangle.2.circlepath")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.imageScale(.small)
|
||||
.foregroundColor(Color.accentColor)
|
||||
Text("Reboot")
|
||||
.font(.caption)
|
||||
|
||||
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.clipShape(Capsule())
|
||||
.confirmationDialog(
|
||||
"Are you sure?",
|
||||
isPresented: $isPresentingRebootConfirm
|
||||
) {
|
||||
Button("Reboot Node?", role: .destructive) {
|
||||
let success = bleManager.sendReboot(destNum: node.num, wantResponse: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.padding(5)
|
||||
Divider()
|
||||
HStack {
|
||||
|
||||
Image(systemName: "clock.badge.checkmark.fill")
|
||||
|
|
@ -254,7 +315,7 @@ struct NodeDetail: View {
|
|||
.symbolRenderingMode(.hierarchical)
|
||||
Text("Time:")
|
||||
.font(.caption)
|
||||
Text("\(mappin.time!, style: .date) \(mappin.time!, style: .time)")
|
||||
DateTimeText(dateTime: mappin.time)
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption)
|
||||
Divider()
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ struct ShareChannel: View {
|
|||
@EnvironmentObject var bleManager: BLEManager
|
||||
@EnvironmentObject var userSettings: UserSettings
|
||||
|
||||
let channelSet = ChannelSet()
|
||||
|
||||
@State private var text = "meshtastic.org"
|
||||
@State private var text = "https://www.meshtastic.org/e/#"
|
||||
var qrCodeImage = QrCodeImage()
|
||||
|
||||
var body: some View {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue