Merge pull request #72 from meshtastic/feature/1.3_Cleanup

Feature/1.3 cleanup
This commit is contained in:
Garth Vander Houwen 2022-06-07 00:02:08 -07:00 committed by GitHub
commit 777f7827f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 675 additions and 791 deletions

View file

@ -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;

View file

@ -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
}
}

View 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
//
//}

View file

@ -44,8 +44,6 @@ class LocalNotificationManager {
UNUserNotificationCenter.current().add(request) { error in
guard error == nil else { return }
print("Notification scheduled! --- ID = \(notification.id)")
}
}
}

View file

@ -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 {

View 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]
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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"),

View file

@ -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 {

View 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")
}
}
}

View 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)
}
}
}

View file

@ -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
})
}

View file

@ -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()

View file

@ -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 {