2025-01-22 12:16:41 -08:00
//
// M e s h P a c k e t s . s w i f t
// M e s h t a s t i c A p p l e
//
// C r e a t e d b y G a r t h V a n d e r H o u w e n o n 5 / 2 7 / 2 2 .
//
2021-10-06 17:51:52 -07:00
import Foundation
2025-01-22 12:16:41 -08:00
import CoreData
import MeshtasticProtobufs
2021-10-06 22:24:45 -07:00
import SwiftUI
2025-01-22 12:16:41 -08:00
import RegexBuilder
2024-06-03 02:17:55 -07:00
import OSLog
2025-01-22 12:16:41 -08:00
#if canImport ( ActivityKit )
import ActivityKit
#endif
func generateMessageMarkdown ( message : String ) -> String {
if ! message . isEmoji ( ) {
let types : NSTextCheckingResult . CheckingType = [ . address , . link , . phoneNumber ]
guard let detector = try ? NSDataDetector ( types : types . rawValue ) else {
return message
}
let matches = detector . matches ( in : message , options : [ ] , range : NSRange ( location : 0 , length : message . utf16 . count ) )
var messageWithMarkdown = message
if matches . count > 0 {
for match in matches {
guard let range = Range ( match . range , in : message ) else { continue }
if match . resultType = = . address {
let address = message [ range ]
let urlEncodedAddress = address . addingPercentEncoding ( withAllowedCharacters : . alphanumerics )
messageWithMarkdown = messageWithMarkdown . replacingOccurrences ( of : address , with : " [ \( address ) ](http://maps.apple.com/?address= \( urlEncodedAddress ? ? " " ) ) " )
} else if match . resultType = = . phoneNumber {
let phone = messageWithMarkdown [ range ]
messageWithMarkdown = messageWithMarkdown . replacingOccurrences ( of : phone , with : " [ \( phone ) ](tel: \( phone ) ) " )
} else if match . resultType = = . link {
let start = match . range . lowerBound
let stop = match . range . upperBound
let url = message [ start . . < stop ]
let absoluteUrl = match . url ? . absoluteString ? ? " "
let markdownUrl = " [ \( url ) ]( \( absoluteUrl ) ) "
messageWithMarkdown = messageWithMarkdown . replacingOccurrences ( of : url , with : markdownUrl )
}
}
}
return messageWithMarkdown
}
return message
}
2021-10-06 22:24:45 -07:00
2025-01-22 12:16:41 -08:00
func localConfig ( config : Config , context : NSManagedObjectContext , nodeNum : Int64 , nodeLongName : String ) {
2021-10-06 22:24:45 -07:00
2025-01-22 12:16:41 -08:00
if config . payloadVariant = = Config . OneOf_PayloadVariant . bluetooth ( config . bluetooth ) {
upsertBluetoothConfigPacket ( config : config . bluetooth , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . device ( config . device ) {
upsertDeviceConfigPacket ( config : config . device , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . display ( config . display ) {
upsertDisplayConfigPacket ( config : config . display , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . lora ( config . lora ) {
upsertLoRaConfigPacket ( config : config . lora , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . network ( config . network ) {
upsertNetworkConfigPacket ( config : config . network , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . position ( config . position ) {
upsertPositionConfigPacket ( config : config . position , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . power ( config . power ) {
upsertPowerConfigPacket ( config : config . power , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . security ( config . security ) {
upsertSecurityConfigPacket ( config : config . security , nodeNum : nodeNum , context : context )
}
}
2021-11-29 15:59:06 -08:00
2025-01-22 12:16:41 -08:00
func moduleConfig ( config : ModuleConfig , context : NSManagedObjectContext , nodeNum : Int64 , nodeLongName : String ) {
2021-10-06 22:24:45 -07:00
2025-01-22 12:16:41 -08:00
if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . ambientLighting ( config . ambientLighting ) {
upsertAmbientLightingModuleConfigPacket ( config : config . ambientLighting , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . cannedMessage ( config . cannedMessage ) {
upsertCannedMessagesModuleConfigPacket ( config : config . cannedMessage , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . detectionSensor ( config . detectionSensor ) {
upsertDetectionSensorModuleConfigPacket ( config : config . detectionSensor , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . externalNotification ( config . externalNotification ) {
upsertExternalNotificationModuleConfigPacket ( config : config . externalNotification , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . mqtt ( config . mqtt ) {
upsertMqttModuleConfigPacket ( config : config . mqtt , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . paxcounter ( config . paxcounter ) {
upsertPaxCounterModuleConfigPacket ( config : config . paxcounter , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . rangeTest ( config . rangeTest ) {
upsertRangeTestModuleConfigPacket ( config : config . rangeTest , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . serial ( config . serial ) {
upsertSerialModuleConfigPacket ( config : config . serial , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . telemetry ( config . telemetry ) {
upsertTelemetryModuleConfigPacket ( config : config . telemetry , nodeNum : nodeNum , context : context )
} else if config . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . storeForward ( config . storeForward ) {
upsertStoreForwardModuleConfigPacket ( config : config . storeForward , nodeNum : nodeNum , context : context )
}
}
func myInfoPacket ( myInfo : MyNodeInfo , peripheralId : String , context : NSManagedObjectContext ) -> MyInfoEntity ? {
let logString = String . localizedStringWithFormat ( " mesh.log.myinfo %@ " . localized , String ( myInfo . myNodeNum ) )
MeshLogger . log ( " ℹ ️ \( logString ) " )
let fetchMyInfoRequest = MyInfoEntity . fetchRequest ( )
fetchMyInfoRequest . predicate = NSPredicate ( format : " myNodeNum == %lld " , Int64 ( myInfo . myNodeNum ) )
do {
let fetchedMyInfo = try context . fetch ( fetchMyInfoRequest )
// N o t F o u n d I n s e r t
if fetchedMyInfo . isEmpty {
let myInfoEntity = MyInfoEntity ( context : context )
myInfoEntity . peripheralId = peripheralId
myInfoEntity . myNodeNum = Int64 ( myInfo . myNodeNum )
myInfoEntity . rebootCount = Int32 ( myInfo . rebootCount )
myInfoEntity . deviceId = myInfo . deviceID
do {
try context . save ( )
Logger . data . info ( " 💾 Saved a new myInfo for node: \( myInfo . myNodeNum . toHex ( ) , privacy : . public ) " )
return myInfoEntity
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " 💥 Error Inserting New Core Data MyInfoEntity: \( nsError , privacy : . public ) " )
}
} else {
fetchedMyInfo [ 0 ] . peripheralId = peripheralId
fetchedMyInfo [ 0 ] . myNodeNum = Int64 ( myInfo . myNodeNum )
fetchedMyInfo [ 0 ] . rebootCount = Int32 ( myInfo . rebootCount )
do {
try context . save ( )
Logger . data . info ( " 💾 Updated myInfo for node: \( myInfo . myNodeNum . toHex ( ) , privacy : . public ) " )
return fetchedMyInfo [ 0 ]
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " 💥 Error Updating Core Data MyInfoEntity: \( nsError , privacy : . public ) " )
2024-11-30 19:09:17 -08:00
}
}
2025-01-22 12:16:41 -08:00
} catch {
Logger . data . error ( " 💥 Fetch MyInfo Error " )
2024-11-30 19:09:17 -08:00
}
2025-01-22 12:16:41 -08:00
return nil
}
func channelPacket ( channel : Channel , fromNum : Int64 , context : NSManagedObjectContext ) {
if channel . isInitialized && channel . hasSettings && channel . role != Channel . Role . disabled {
2021-11-29 15:59:06 -08:00
2025-01-22 12:16:41 -08:00
let logString = String . localizedStringWithFormat ( " mesh.log.channel.received %d %@ " . localized , channel . index , String ( fromNum ) )
MeshLogger . log ( " 🎛️ \( logString ) " )
let fetchedMyInfoRequest = MyInfoEntity . fetchRequest ( )
fetchedMyInfoRequest . predicate = NSPredicate ( format : " myNodeNum == %lld " , fromNum )
do {
let fetchedMyInfo = try context . fetch ( fetchedMyInfoRequest )
if fetchedMyInfo . count = = 1 {
let newChannel = ChannelEntity ( context : context )
newChannel . id = Int32 ( channel . index )
newChannel . index = Int32 ( channel . index )
newChannel . uplinkEnabled = channel . settings . uplinkEnabled
newChannel . downlinkEnabled = channel . settings . downlinkEnabled
newChannel . name = channel . settings . name
newChannel . role = Int32 ( channel . role . rawValue )
newChannel . psk = channel . settings . psk
if channel . settings . hasModuleSettings {
newChannel . positionPrecision = Int32 ( truncatingIfNeeded : channel . settings . moduleSettings . positionPrecision )
newChannel . mute = channel . settings . moduleSettings . isClientMuted
}
guard let mutableChannels = fetchedMyInfo [ 0 ] . channels ! . mutableCopy ( ) as ? NSMutableOrderedSet else {
return
}
if let oldChannel = mutableChannels . first ( where : { ( $0 as AnyObject ) . index = = newChannel . index } ) as ? ChannelEntity {
let index = mutableChannels . index ( of : oldChannel as Any )
mutableChannels . replaceObject ( at : index , with : newChannel )
} else {
mutableChannels . add ( newChannel )
}
fetchedMyInfo [ 0 ] . channels = mutableChannels . copy ( ) as ? NSOrderedSet
if newChannel . name ? . lowercased ( ) = = " admin " {
fetchedMyInfo [ 0 ] . adminIndex = newChannel . index
}
context . refresh ( newChannel , mergeChanges : true )
do {
try context . save ( )
} catch {
Logger . data . error ( " 💥 Failed to save channel: \( error . localizedDescription , privacy : . public ) " )
}
Logger . data . info ( " 💾 Updated MyInfo channel \( channel . index ) from Channel App Packet For: \( fetchedMyInfo [ 0 ] . myNodeNum ) " )
} else if channel . role . rawValue > 0 {
Logger . data . error ( " 💥Trying to save a channel to a MyInfo that does not exist: \( fromNum . toHex ( ) , privacy : . public ) " )
2024-11-30 19:09:17 -08:00
}
2025-01-22 12:16:41 -08:00
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " 💥 Error Saving MyInfo Channel from ADMIN_APP \( nsError , privacy : . public ) " )
2024-11-30 19:09:17 -08:00
}
}
2025-01-22 12:16:41 -08:00
}
func deviceMetadataPacket ( metadata : DeviceMetadata , fromNum : Int64 , sessionPasskey : Data ? = Data ( ) , context : NSManagedObjectContext ) {
2021-11-29 15:59:06 -08:00
2025-01-22 12:16:41 -08:00
if metadata . isInitialized {
let logString = String . localizedStringWithFormat ( " mesh.log.device.metadata.received %@ " . localized , fromNum . toHex ( ) )
MeshLogger . log ( " 🏷️ \( logString ) " )
2024-09-03 22:50:08 -07:00
2025-01-22 12:16:41 -08:00
let fetchedNodeRequest = NodeInfoEntity . fetchRequest ( )
fetchedNodeRequest . predicate = NSPredicate ( format : " num == %lld " , fromNum )
2024-10-05 15:50:57 -07:00
2025-01-22 12:16:41 -08:00
do {
let fetchedNode = try context . fetch ( fetchedNodeRequest )
let newMetadata = DeviceMetadataEntity ( context : context )
newMetadata . time = Date ( )
newMetadata . deviceStateVersion = Int32 ( metadata . deviceStateVersion )
newMetadata . canShutdown = metadata . canShutdown
newMetadata . hasWifi = metadata . hasWifi_p
newMetadata . hasBluetooth = metadata . hasBluetooth_p
newMetadata . hasEthernet = metadata . hasEthernet_p
newMetadata . role = Int32 ( metadata . role . rawValue )
newMetadata . positionFlags = Int32 ( metadata . positionFlags )
// S w i f t d o e s s t r i n g s w e i r d , t h i s d o e s w o r k t o g e t t h e v e r s i o n w i t h o u t t h e g i t h u b h a s h
let lastDotIndex = metadata . firmwareVersion . lastIndex ( of : " . " )
var version = metadata . firmwareVersion [ . . . ( lastDotIndex ? ? String . Index ( utf16Offset : 6 , in : metadata . firmwareVersion ) ) ]
version = version . dropLast ( )
newMetadata . firmwareVersion = String ( version )
if fetchedNode . count > 0 {
fetchedNode [ 0 ] . metadata = newMetadata
} else {
if fromNum > 0 {
let newNode = createNodeInfo ( num : Int64 ( fromNum ) , context : context )
newNode . metadata = newMetadata
}
2024-11-30 19:09:17 -08:00
}
2025-01-22 12:16:41 -08:00
if sessionPasskey ? . count != 0 {
fetchedNode [ 0 ] . sessionPasskey = sessionPasskey
fetchedNode [ 0 ] . sessionExpiration = Date ( ) . addingTimeInterval ( 300 )
}
do {
try context . save ( )
} catch {
Logger . data . error ( " 💥 Failed to save device metadata: \( error . localizedDescription , privacy : . public ) " )
}
Logger . data . info ( " 💾 Updated Device Metadata from Admin App Packet For: \( fromNum . toHex ( ) , privacy : . public ) " )
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " Error Saving MyInfo Channel from ADMIN_APP \( nsError , privacy : . public ) " )
}
}
}
func nodeInfoPacket ( nodeInfo : NodeInfo , channel : UInt32 , context : NSManagedObjectContext ) -> NodeInfoEntity ? {
let logString = String . localizedStringWithFormat ( " mesh.log.nodeinfo.received %@ " . localized , String ( nodeInfo . num ) )
MeshLogger . log ( " 📟 \( logString ) " )
guard nodeInfo . num > 0 else { return nil }
let fetchNodeInfoRequest = NodeInfoEntity . fetchRequest ( )
fetchNodeInfoRequest . predicate = NSPredicate ( format : " num == %lld " , Int64 ( nodeInfo . num ) )
do {
let fetchedNode = try context . fetch ( fetchNodeInfoRequest )
// N o t F o u n d I n s e r t
if fetchedNode . isEmpty && nodeInfo . num > 0 {
let newNode = NodeInfoEntity ( context : context )
newNode . id = Int64 ( nodeInfo . num )
newNode . num = Int64 ( nodeInfo . num )
newNode . channel = Int32 ( nodeInfo . channel )
newNode . favorite = nodeInfo . isFavorite
newNode . ignored = nodeInfo . isIgnored
newNode . hopsAway = Int32 ( nodeInfo . hopsAway )
2024-05-29 16:40:07 -05:00
2025-01-22 12:16:41 -08:00
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 )
2023-08-21 10:24:17 -05:00
}
2025-01-22 12:16:41 -08:00
newNode . firstHeard = Date ( timeIntervalSince1970 : TimeInterval ( Int64 ( nodeInfo . lastHeard ) ) )
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 . hwModel = String ( describing : nodeInfo . user . hwModel ) . uppercased ( )
newUser . hwModelId = Int32 ( nodeInfo . user . hwModel . rawValue )
Task {
Api ( ) . loadDeviceHardwareData { ( hw ) in
let dh = hw . first ( where : { $0 . hwModel = = newUser . hwModelId } )
newUser . hwDisplayName = dh ? . displayName
}
}
newUser . isLicensed = nodeInfo . user . isLicensed
newUser . role = Int32 ( nodeInfo . user . role . rawValue )
if ! nodeInfo . user . publicKey . isEmpty {
newUser . pkiEncrypted = true
newUser . publicKey = nodeInfo . user . publicKey
}
newNode . user = newUser
} else if nodeInfo . num > Constants . minimumNodeNum {
let newUser = createUser ( num : Int64 ( nodeInfo . num ) , context : context )
newNode . user = newUser
2024-01-20 14:05:29 -08:00
}
2025-01-22 12:16:41 -08:00
if ( nodeInfo . position . longitudeI != 0 && nodeInfo . position . latitudeI != 0 ) && ( nodeInfo . position . latitudeI != 373346000 && nodeInfo . position . longitudeI != - 1220090000 ) {
let position = PositionEntity ( context : context )
position . latest = true
position . seqNo = Int32 ( nodeInfo . position . seqNumber )
position . latitudeI = nodeInfo . position . latitudeI
position . longitudeI = nodeInfo . position . longitudeI
position . altitude = nodeInfo . position . altitude
position . satsInView = Int32 ( nodeInfo . position . satsInView )
position . speed = Int32 ( nodeInfo . position . groundSpeed )
position . heading = Int32 ( nodeInfo . position . groundTrack )
position . time = Date ( timeIntervalSince1970 : TimeInterval ( Int64 ( nodeInfo . position . time ) ) )
var newPostions = [ PositionEntity ] ( )
newPostions . append ( position )
newNode . positions ? = NSOrderedSet ( array : newPostions )
2024-09-03 22:50:08 -07:00
}
2025-01-22 12:16:41 -08:00
// L o o k f o r a M y I n f o
let fetchMyInfoRequest = MyInfoEntity . fetchRequest ( )
fetchMyInfoRequest . predicate = NSPredicate ( format : " myNodeNum == %lld " , Int64 ( nodeInfo . num ) )
do {
let fetchedMyInfo = try context . fetch ( fetchMyInfoRequest )
if fetchedMyInfo . count > 0 {
newNode . myInfo = fetchedMyInfo [ 0 ]
}
do {
try context . save ( )
Logger . data . info ( " 💾 Saved a new Node Info For: \( String ( nodeInfo . num ) ) " )
return newNode
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " Error Saving Core Data NodeInfoEntity: \( nsError ) " )
}
} catch {
Logger . data . error ( " Fetch MyInfo Error " )
2024-09-03 22:50:08 -07:00
}
2025-01-22 12:16:41 -08:00
} else if 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
fetchedNode [ 0 ] . channel = Int32 ( nodeInfo . channel )
fetchedNode [ 0 ] . favorite = nodeInfo . isFavorite
fetchedNode [ 0 ] . ignored = nodeInfo . isIgnored
fetchedNode [ 0 ] . hopsAway = Int32 ( nodeInfo . hopsAway )
if nodeInfo . hasUser {
if fetchedNode [ 0 ] . user = = nil {
fetchedNode [ 0 ] . user = UserEntity ( context : context )
}
// S e t t h e p u b l i c k e y f o r a u s e r i f i t i s e m p t y , d o n ' t u p d a t e
if fetchedNode [ 0 ] . user ? . publicKey = = nil && ! nodeInfo . user . publicKey . isEmpty {
fetchedNode [ 0 ] . user ? . pkiEncrypted = true
fetchedNode [ 0 ] . user ? . publicKey = nodeInfo . user . publicKey
}
fetchedNode [ 0 ] . user ! . userId = nodeInfo . user . id
fetchedNode [ 0 ] . user ! . num = Int64 ( nodeInfo . num )
fetchedNode [ 0 ] . user ! . numString = String ( nodeInfo . num )
fetchedNode [ 0 ] . user ! . longName = nodeInfo . user . longName
fetchedNode [ 0 ] . user ! . shortName = nodeInfo . user . shortName
fetchedNode [ 0 ] . user ! . isLicensed = nodeInfo . user . isLicensed
fetchedNode [ 0 ] . user ! . role = Int32 ( nodeInfo . user . role . rawValue )
fetchedNode [ 0 ] . user ! . hwModel = String ( describing : nodeInfo . user . hwModel ) . uppercased ( )
fetchedNode [ 0 ] . user ! . hwModelId = Int32 ( nodeInfo . user . hwModel . rawValue )
Task {
Api ( ) . loadDeviceHardwareData { ( hw ) in
let dh = hw . first ( where : { $0 . hwModel = = fetchedNode [ 0 ] . user ! . hwModelId } )
fetchedNode [ 0 ] . user ! . hwDisplayName = dh ? . displayName
}
}
} else {
if fetchedNode [ 0 ] . user = = nil && nodeInfo . num > Constants . minimumNodeNum {
let newUser = createUser ( num : Int64 ( nodeInfo . num ) , context : context )
fetchedNode [ 0 ] . user = newUser
}
2024-09-03 22:50:08 -07:00
}
2025-01-22 12:16:41 -08:00
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
guard let mutableTelemetries = fetchedNode [ 0 ] . telemetries ! . mutableCopy ( ) as ? NSMutableOrderedSet else {
return nil
}
fetchedNode [ 0 ] . telemetries = mutableTelemetries . copy ( ) as ? NSOrderedSet
2025-01-10 08:32:42 -08:00
}
2024-09-03 22:50:08 -07:00
2025-01-22 12:16:41 -08:00
if nodeInfo . hasPosition {
2025-01-08 13:37:49 -08:00
2025-01-22 12:16:41 -08:00
if ( nodeInfo . position . longitudeI != 0 && nodeInfo . position . latitudeI != 0 ) && ( nodeInfo . position . latitudeI != 373346000 && nodeInfo . position . longitudeI != - 1220090000 ) {
2021-10-06 22:24:45 -07:00
2025-01-22 12:16:41 -08:00
let position = PositionEntity ( context : context )
position . latitudeI = nodeInfo . position . latitudeI
position . longitudeI = nodeInfo . position . longitudeI
position . altitude = nodeInfo . position . altitude
position . satsInView = Int32 ( nodeInfo . position . satsInView )
position . time = Date ( timeIntervalSince1970 : TimeInterval ( Int64 ( nodeInfo . position . time ) ) )
guard let mutablePositions = fetchedNode [ 0 ] . positions ! . mutableCopy ( ) as ? NSMutableOrderedSet else {
return nil
}
fetchedNode [ 0 ] . positions = mutablePositions . copy ( ) as ? NSOrderedSet
2024-07-10 21:17:14 -05:00
}
2025-01-22 12:16:41 -08:00
}
// L o o k f o r a M y I n f o
let fetchMyInfoRequest = MyInfoEntity . fetchRequest ( )
fetchMyInfoRequest . predicate = NSPredicate ( format : " myNodeNum == %lld " , Int64 ( nodeInfo . num ) )
do {
let fetchedMyInfo = try context . fetch ( fetchMyInfoRequest )
if fetchedMyInfo . count > 0 {
fetchedNode [ 0 ] . myInfo = fetchedMyInfo [ 0 ]
}
do {
try context . save ( )
Logger . data . info ( " 💾 [NodeInfo] saved for \( nodeInfo . num . toHex ( ) , privacy : . public ) " )
return fetchedNode [ 0 ]
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " 💥 Error Saving Core Data NodeInfoEntity: \( nsError , privacy : . public ) " )
}
} catch {
Logger . data . error ( " 💥 Fetch MyInfo Error " )
2024-11-30 19:09:17 -08:00
}
}
2025-01-22 12:16:41 -08:00
} catch {
Logger . data . error ( " 💥 Fetch NodeInfoEntity Error " )
2024-11-30 19:09:17 -08:00
}
2025-01-22 12:16:41 -08:00
return nil
}
func adminAppPacket ( packet : MeshPacket , context : NSManagedObjectContext ) {
if let adminMessage = try ? AdminMessage ( serializedBytes : packet . decoded . payload ) {
if adminMessage . payloadVariant = = AdminMessage . OneOf_PayloadVariant . getCannedMessageModuleMessagesResponse ( adminMessage . getCannedMessageModuleMessagesResponse ) {
2021-11-29 15:59:06 -08:00
2025-01-22 12:16:41 -08:00
if let cmmc = try ? CannedMessageModuleConfig ( serializedBytes : packet . decoded . payload ) {
2021-10-06 22:24:45 -07:00
2025-01-22 12:16:41 -08:00
if ! cmmc . messages . isEmpty {
let logString = String . localizedStringWithFormat ( " mesh.log.cannedmessages.messages.received %@ " . localized , packet . from . toHex ( ) )
MeshLogger . log ( " 🥫 \( logString ) " )
let fetchNodeRequest = NodeInfoEntity . fetchRequest ( )
fetchNodeRequest . predicate = NSPredicate ( format : " num == %lld " , Int64 ( packet . from ) )
do {
let fetchedNode = try context . fetch ( fetchNodeRequest )
if fetchedNode . count = = 1 {
let messages = String ( cmmc . textFormatString ( ) )
. replacingOccurrences ( of : " 11: " , with : " " )
. replacingOccurrences ( of : " \" " , with : " " )
. trimmingCharacters ( in : . whitespacesAndNewlines )
fetchedNode [ 0 ] . cannedMessageConfig ? . messages = messages
do {
try context . save ( )
Logger . data . info ( " 💾 Updated Canned Messages Messages For: \( fetchedNode [ 0 ] . num . toHex ( ) , privacy : . public ) " )
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " 💥 Error Saving NodeInfoEntity from POSITION_APP \( nsError , privacy : . public ) " )
}
}
} catch {
Logger . data . error ( " 💥 Error Deserializing ADMIN_APP packet. " )
}
}
}
} else if adminMessage . payloadVariant = = AdminMessage . OneOf_PayloadVariant . getChannelResponse ( adminMessage . getChannelResponse ) {
channelPacket ( channel : adminMessage . getChannelResponse , fromNum : Int64 ( packet . from ) , context : context )
} else if adminMessage . payloadVariant = = AdminMessage . OneOf_PayloadVariant . getDeviceMetadataResponse ( adminMessage . getDeviceMetadataResponse ) {
deviceMetadataPacket ( metadata : adminMessage . getDeviceMetadataResponse , fromNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
} else if adminMessage . payloadVariant = = AdminMessage . OneOf_PayloadVariant . getConfigResponse ( adminMessage . getConfigResponse ) {
let config = adminMessage . getConfigResponse
if config . payloadVariant = = Config . OneOf_PayloadVariant . bluetooth ( config . bluetooth ) {
upsertBluetoothConfigPacket ( config : config . bluetooth , nodeNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . device ( config . device ) {
upsertDeviceConfigPacket ( config : config . device , nodeNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . display ( config . display ) {
upsertDisplayConfigPacket ( config : config . display , nodeNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . lora ( config . lora ) {
upsertLoRaConfigPacket ( config : config . lora , nodeNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . network ( config . network ) {
upsertNetworkConfigPacket ( config : config . network , nodeNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . position ( config . position ) {
upsertPositionConfigPacket ( config : config . position , nodeNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . power ( config . power ) {
upsertPowerConfigPacket ( config : config . power , nodeNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
} else if config . payloadVariant = = Config . OneOf_PayloadVariant . security ( config . security ) {
upsertSecurityConfigPacket ( config : config . security , nodeNum : Int64 ( packet . from ) , sessionPasskey : adminMessage . sessionPasskey , context : context )
2024-11-30 19:09:17 -08:00
}
2025-01-22 12:16:41 -08:00
} else if adminMessage . payloadVariant = = AdminMessage . OneOf_PayloadVariant . getModuleConfigResponse ( adminMessage . getModuleConfigResponse ) {
let moduleConfig = adminMessage . getModuleConfigResponse
if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . ambientLighting ( moduleConfig . ambientLighting ) {
upsertAmbientLightingModuleConfigPacket ( config : moduleConfig . ambientLighting , nodeNum : Int64 ( packet . from ) , context : context )
} else if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . cannedMessage ( moduleConfig . cannedMessage ) {
upsertCannedMessagesModuleConfigPacket ( config : moduleConfig . cannedMessage , nodeNum : Int64 ( packet . from ) , context : context )
} else if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . detectionSensor ( moduleConfig . detectionSensor ) {
upsertDetectionSensorModuleConfigPacket ( config : moduleConfig . detectionSensor , nodeNum : Int64 ( packet . from ) , context : context )
} else if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . externalNotification ( moduleConfig . externalNotification ) {
upsertExternalNotificationModuleConfigPacket ( config : moduleConfig . externalNotification , nodeNum : Int64 ( packet . from ) , context : context )
} else if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . mqtt ( moduleConfig . mqtt ) {
upsertMqttModuleConfigPacket ( config : moduleConfig . mqtt , nodeNum : Int64 ( packet . from ) , context : context )
} else if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . rangeTest ( moduleConfig . rangeTest ) {
upsertRangeTestModuleConfigPacket ( config : moduleConfig . rangeTest , nodeNum : Int64 ( packet . from ) , context : context )
} else if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . serial ( moduleConfig . serial ) {
upsertSerialModuleConfigPacket ( config : moduleConfig . serial , nodeNum : Int64 ( packet . from ) , context : context )
} else if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . storeForward ( moduleConfig . storeForward ) {
upsertStoreForwardModuleConfigPacket ( config : moduleConfig . storeForward , nodeNum : Int64 ( packet . from ) , context : context )
} else if moduleConfig . payloadVariant = = ModuleConfig . OneOf_PayloadVariant . telemetry ( moduleConfig . telemetry ) {
upsertTelemetryModuleConfigPacket ( config : moduleConfig . telemetry , nodeNum : Int64 ( packet . from ) , context : context )
}
} else if adminMessage . payloadVariant = = AdminMessage . OneOf_PayloadVariant . getRingtoneResponse ( adminMessage . getRingtoneResponse ) {
let ringtone = adminMessage . getRingtoneResponse
upsertRtttlConfigPacket ( ringtone : ringtone , nodeNum : Int64 ( packet . from ) , context : context )
} else {
MeshLogger . log ( " 🕸️ MESH PACKET received Admin App UNHANDLED \( ( try ? packet . decoded . jsonString ( ) ) ? ? " JSON Decode Failure " ) " )
2024-11-30 19:09:17 -08:00
}
2025-01-22 12:16:41 -08:00
// S a v e a n a c k f o r t h e a d m i n m e s s a g e l o g f o r e a c h a d m i n m e s s a g e r e s p o n s e r e c e i v e d a s w e s t o p p e d s e n d i n g a c k s i f t h e r e i s a l s o a r e s p o n s e t o r e d u c e a i r t i m e .
adminResponseAck ( packet : packet , context : context )
2024-11-30 19:09:17 -08:00
}
2025-01-22 12:16:41 -08:00
}
func adminResponseAck ( packet : MeshPacket , context : NSManagedObjectContext ) {
2021-11-29 15:59:06 -08:00
2025-01-22 12:16:41 -08:00
let fetchedAdminMessageRequest = MessageEntity . fetchRequest ( )
fetchedAdminMessageRequest . predicate = NSPredicate ( format : " messageId == %lld " , packet . decoded . requestID )
do {
let fetchedMessage = try context . fetch ( fetchedAdminMessageRequest )
if fetchedMessage . count > 0 {
fetchedMessage [ 0 ] . ackTimestamp = Int32 ( Date ( ) . timeIntervalSince1970 )
fetchedMessage [ 0 ] . ackError = Int32 ( RoutingError . none . rawValue )
fetchedMessage [ 0 ] . receivedACK = true
fetchedMessage [ 0 ] . realACK = true
fetchedMessage [ 0 ] . ackSNR = packet . rxSnr
if fetchedMessage [ 0 ] . fromUser != nil {
fetchedMessage [ 0 ] . fromUser ? . objectWillChange . send ( )
}
do {
try context . save ( )
} catch {
Logger . data . error ( " Failed to save admin message response as an ack: \( error . localizedDescription ) " )
}
}
} catch {
Logger . data . error ( " Failed to fetch admin message by requestID: \( error . localizedDescription ) " )
}
2021-10-06 22:24:45 -07:00
}
2025-01-22 12:16:41 -08:00
func paxCounterPacket ( packet : MeshPacket , context : NSManagedObjectContext ) {
let logString = String . localizedStringWithFormat ( " mesh.log.paxcounter %@ " . localized , String ( packet . from ) )
MeshLogger . log ( " 🧑🤝🧑 \( logString ) " )
let fetchNodeInfoRequest = NodeInfoEntity . fetchRequest ( )
fetchNodeInfoRequest . predicate = NSPredicate ( format : " num == %lld " , Int64 ( packet . from ) )
do {
let fetchedNode = try context . fetch ( fetchNodeInfoRequest )
if let paxMessage = try ? Paxcount ( serializedBytes : packet . decoded . payload ) {
2021-10-06 22:24:45 -07:00
2025-01-22 12:16:41 -08:00
let newPax = PaxCounterEntity ( context : context )
newPax . ble = Int32 ( truncatingIfNeeded : paxMessage . ble )
newPax . wifi = Int32 ( truncatingIfNeeded : paxMessage . wifi )
newPax . uptime = Int32 ( truncatingIfNeeded : paxMessage . uptime )
newPax . time = Date ( )
if fetchedNode . count > 0 {
guard let mutablePax = fetchedNode [ 0 ] . pax ! . mutableCopy ( ) as ? NSMutableOrderedSet else {
return
}
mutablePax . add ( newPax )
fetchedNode [ 0 ] . pax = mutablePax
do {
try context . save ( )
} catch {
Logger . data . error ( " Failed to save pax: \( error . localizedDescription ) " )
}
} else {
Logger . data . info ( " Node Info Not Found " )
}
}
} catch {
}
}
func routingPacket ( packet : MeshPacket , connectedNodeNum : Int64 , context : NSManagedObjectContext ) {
if let routingMessage = try ? Routing ( serializedBytes : packet . decoded . payload ) {
let routingError = RoutingError ( rawValue : routingMessage . errorReason . rawValue )
let routingErrorString = routingError ? . display ? ? " unknown " . localized
let logString = String . localizedStringWithFormat ( " mesh.log.routing.message %@ %@ " . localized , String ( packet . decoded . requestID ) , routingErrorString )
MeshLogger . log ( " 🕸️ \( logString ) " )
let fetchMessageRequest = MessageEntity . fetchRequest ( )
fetchMessageRequest . predicate = NSPredicate ( format : " messageId == %lld " , Int64 ( packet . decoded . requestID ) )
do {
let fetchedMessage = try context . fetch ( fetchMessageRequest )
if fetchedMessage . count > 0 {
if fetchedMessage [ 0 ] . toUser != nil {
// R e a l A C K f r o m D M R e c i p i e n t
if packet . to != packet . from {
fetchedMessage [ 0 ] . realACK = true
}
}
fetchedMessage [ 0 ] . ackError = Int32 ( routingMessage . errorReason . rawValue )
if routingMessage . errorReason = = Routing . Error . none {
fetchedMessage [ 0 ] . receivedACK = true
}
fetchedMessage [ 0 ] . ackSNR = packet . rxSnr
if packet . rxTime > 0 {
fetchedMessage [ 0 ] . ackTimestamp = Int32 ( truncatingIfNeeded : packet . rxTime )
} else {
fetchedMessage [ 0 ] . ackTimestamp = Int32 ( Date ( ) . timeIntervalSince1970 )
}
if fetchedMessage [ 0 ] . toUser != nil {
fetchedMessage [ 0 ] . toUser ! . objectWillChange . send ( )
} else {
let fetchMyInfoRequest = MyInfoEntity . fetchRequest ( )
fetchMyInfoRequest . predicate = NSPredicate ( format : " myNodeNum == %lld " , connectedNodeNum )
do {
let fetchedMyInfo = try context . fetch ( fetchMyInfoRequest )
if fetchedMyInfo . count > 0 {
for ch in fetchedMyInfo [ 0 ] . channels ! . array as ? [ ChannelEntity ] ? ? [ ] where ch . index = = packet . channel {
ch . objectWillChange . send ( )
}
}
} catch { }
}
} else {
return
}
try context . save ( )
Logger . data . info ( " 💾 ACK Saved for Message: \( packet . decoded . requestID ) " )
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " Error Saving ACK for message: \( packet . id ) Error: \( nsError ) " )
}
}
}
func telemetryPacket ( packet : MeshPacket , connectedNode : Int64 , context : NSManagedObjectContext ) {
if let telemetryMessage = try ? Telemetry ( serializedBytes : packet . decoded . payload ) {
let logString = String . localizedStringWithFormat ( " mesh.log.telemetry.received %@ " . localized , String ( packet . from ) )
MeshLogger . log ( " 📈 \( logString ) " )
if telemetryMessage . variant != Telemetry . OneOf_Variant . deviceMetrics ( telemetryMessage . deviceMetrics ) && telemetryMessage . variant != Telemetry . OneOf_Variant . environmentMetrics ( telemetryMessage . environmentMetrics ) && telemetryMessage . variant != Telemetry . OneOf_Variant . localStats ( telemetryMessage . localStats ) {
// / O t h e r u n h a n d l e d t e l e m e t r y p a c k e t s
return
}
let telemetry = TelemetryEntity ( context : context )
let fetchNodeTelemetryRequest = NodeInfoEntity . fetchRequest ( )
fetchNodeTelemetryRequest . predicate = NSPredicate ( format : " num == %lld " , Int64 ( packet . from ) )
do {
let fetchedNode = try context . fetch ( fetchNodeTelemetryRequest )
if fetchedNode . count = = 1 {
// / C u r r e n t l y o n l y D e v i c e M e t r i c s a n d E n v i r o n m e n t T e l e m e t r y a r e s u p p o r t e d i n t h e a p p
if telemetryMessage . variant = = Telemetry . OneOf_Variant . deviceMetrics ( telemetryMessage . deviceMetrics ) {
// D e v i c e M e t r i c s
telemetry . airUtilTx = telemetryMessage . deviceMetrics . airUtilTx
telemetry . channelUtilization = telemetryMessage . deviceMetrics . channelUtilization
telemetry . batteryLevel = Int32 ( telemetryMessage . deviceMetrics . batteryLevel )
telemetry . voltage = telemetryMessage . deviceMetrics . voltage
telemetry . uptimeSeconds = Int32 ( telemetryMessage . deviceMetrics . uptimeSeconds )
telemetry . metricsType = 0
Logger . statistics . info ( " 📈 [Mesh Statistics] Channel Utilization: \( telemetryMessage . deviceMetrics . channelUtilization , privacy : . public ) Airtime: \( telemetryMessage . deviceMetrics . airUtilTx , privacy : . public ) for Node: \( packet . from . toHex ( ) , privacy : . public ) " )
} else if telemetryMessage . variant = = Telemetry . OneOf_Variant . environmentMetrics ( telemetryMessage . environmentMetrics ) {
// E n v i r o n m e n t M e t r i c s
telemetry . barometricPressure = telemetryMessage . environmentMetrics . barometricPressure
telemetry . current = telemetryMessage . environmentMetrics . current
telemetry . iaq = Int32 ( truncatingIfNeeded : telemetryMessage . environmentMetrics . iaq )
telemetry . gasResistance = telemetryMessage . environmentMetrics . gasResistance
telemetry . relativeHumidity = telemetryMessage . environmentMetrics . relativeHumidity
telemetry . temperature = telemetryMessage . environmentMetrics . temperature
telemetry . current = telemetryMessage . environmentMetrics . current
telemetry . voltage = telemetryMessage . environmentMetrics . voltage
telemetry . weight = telemetryMessage . environmentMetrics . weight
telemetry . windSpeed = telemetryMessage . environmentMetrics . windSpeed
telemetry . windGust = telemetryMessage . environmentMetrics . windGust
telemetry . windLull = telemetryMessage . environmentMetrics . windLull
telemetry . windDirection = Int32 ( truncatingIfNeeded : telemetryMessage . environmentMetrics . windDirection )
telemetry . metricsType = 1
} else if telemetryMessage . variant = = Telemetry . OneOf_Variant . localStats ( telemetryMessage . localStats ) {
// L o c a l S t a t s f o r L i v e a c t i v i t y
telemetry . uptimeSeconds = Int32 ( telemetryMessage . localStats . uptimeSeconds )
telemetry . channelUtilization = telemetryMessage . localStats . channelUtilization
telemetry . airUtilTx = telemetryMessage . localStats . airUtilTx
telemetry . numPacketsTx = Int32 ( truncatingIfNeeded : telemetryMessage . localStats . numPacketsTx )
telemetry . numPacketsRx = Int32 ( truncatingIfNeeded : telemetryMessage . localStats . numPacketsRx )
telemetry . numPacketsRxBad = Int32 ( truncatingIfNeeded : telemetryMessage . localStats . numPacketsRxBad )
telemetry . numRxDupe = Int32 ( truncatingIfNeeded : telemetryMessage . localStats . numRxDupe )
telemetry . numTxRelay = Int32 ( truncatingIfNeeded : telemetryMessage . localStats . numTxRelay )
telemetry . numTxRelayCanceled = Int32 ( truncatingIfNeeded : telemetryMessage . localStats . numTxRelayCanceled )
telemetry . numOnlineNodes = Int32 ( truncatingIfNeeded : telemetryMessage . localStats . numOnlineNodes )
telemetry . numTotalNodes = Int32 ( truncatingIfNeeded : telemetryMessage . localStats . numTotalNodes )
telemetry . metricsType = 4
Logger . statistics . info ( " 📈 [Mesh Statistics] Channel Utilization: \( telemetryMessage . localStats . channelUtilization , privacy : . public ) Airtime: \( telemetryMessage . localStats . airUtilTx , privacy : . public ) Packets Sent: \( telemetryMessage . localStats . numPacketsTx , privacy : . public ) Packets Received: \( telemetryMessage . localStats . numPacketsRx , privacy : . public ) Bad Packets Received: \( telemetryMessage . localStats . numPacketsRxBad , privacy : . public ) Nodes Online: \( telemetryMessage . localStats . numOnlineNodes , privacy : . public ) of \( telemetryMessage . localStats . numTotalNodes , privacy : . public ) nodes for Node: \( packet . from . toHex ( ) , privacy : . public ) " )
}
telemetry . snr = packet . rxSnr
telemetry . rssi = packet . rxRssi
telemetry . time = Date ( timeIntervalSince1970 : TimeInterval ( Int64 ( truncatingIfNeeded : telemetryMessage . time ) ) )
guard let mutableTelemetries = fetchedNode [ 0 ] . telemetries ! . mutableCopy ( ) as ? NSMutableOrderedSet else {
return
}
mutableTelemetries . add ( telemetry )
if packet . rxTime > 0 {
fetchedNode [ 0 ] . lastHeard = Date ( timeIntervalSince1970 : TimeInterval ( packet . rxTime ) )
} else {
fetchedNode [ 0 ] . lastHeard = Date ( )
}
fetchedNode [ 0 ] . telemetries = mutableTelemetries . copy ( ) as ? NSOrderedSet
}
try context . save ( )
Logger . data . info ( " 💾 [TelemetryEntity] of type \( MetricsTypes ( rawValue : Int ( telemetry . metricsType ) ) ? . name ? ? " Unknown Metrics Type " ) Saved for Node: \( packet . from . toHex ( ) ) " )
if telemetry . metricsType = = 0 {
// C o n n e c t e d D e v i c e M e t r i c s
// - - - - - - - - - - - - - - - - - - - - - - - -
// L o w B a t t e r y n o t i f i c a t i o n
if connectedNode = = Int64 ( packet . from ) {
if UserDefaults . lowBatteryNotifications && telemetry . batteryLevel > 0 && telemetry . batteryLevel < 4 {
let manager = LocalNotificationManager ( )
manager . notifications = [
Notification (
id : ( " notification.id. \( UUID ( ) . uuidString ) " ) ,
title : " Critically Low Battery! " ,
subtitle : " AKA \( telemetry . nodeTelemetry ? . user ? . shortName ? ? " UNK " ) " ,
content : " Time to charge your radio, there is \( telemetry . batteryLevel ) % battery remaining. " ,
target : " nodes " ,
path : " meshtastic:///nodes?nodenum= \( telemetry . nodeTelemetry ? . num ? ? 0 ) "
)
]
manager . schedule ( )
}
}
} else if telemetry . metricsType = = 4 {
// U p d a t e o u r l i v e a c t i v i t y i f t h e r e i s o n e r u n n i n g , n o t a v a i l a b l e o n m a c
#if ! targetEnvironment ( macCatalyst )
#if canImport ( ActivityKit )
let fifteenMinutesLater = Calendar . current . date ( byAdding : . minute , value : ( Int ( 15 ) ) , to : Date ( ) ) !
let date = Date . now . . . fifteenMinutesLater
let updatedMeshStatus = MeshActivityAttributes . MeshActivityStatus ( uptimeSeconds : UInt32 ( telemetry . uptimeSeconds ) ,
channelUtilization : telemetry . channelUtilization ,
airtime : telemetry . airUtilTx ,
sentPackets : UInt32 ( telemetry . numPacketsTx ) ,
receivedPackets : UInt32 ( telemetry . numPacketsRx ) ,
badReceivedPackets : UInt32 ( telemetry . numPacketsRxBad ) ,
dupeReceivedPackets : UInt32 ( telemetry . numRxDupe ) ,
packetsSentRelay : UInt32 ( telemetry . numTxRelay ) ,
packetsCanceledRelay : UInt32 ( telemetry . numTxRelayCanceled ) ,
nodesOnline : UInt32 ( telemetry . numOnlineNodes ) ,
totalNodes : UInt32 ( telemetry . numTotalNodes ) ,
timerRange : date )
let alertConfiguration = AlertConfiguration ( title : " Mesh activity update " , body : " Updated Node Stats Data. " , sound : . default )
let updatedContent = ActivityContent ( state : updatedMeshStatus , staleDate : nil )
let meshActivity = Activity < MeshActivityAttributes > . activities . first ( where : { $0 . attributes . nodeNum = = connectedNode } )
if meshActivity != nil {
Task {
await meshActivity ? . update ( updatedContent , alertConfiguration : alertConfiguration )
// a w a i t m e s h A c t i v i t y ? . u p d a t e ( u p d a t e d C o n t e n t )
Logger . services . debug ( " Updated live activity. " )
}
}
#endif
#endif
}
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " 💥 Error Saving Telemetry for Node \( packet . from , privacy : . public ) Error: \( nsError , privacy : . public ) " )
}
} else {
Logger . data . error ( " 💥 Error Fetching NodeInfoEntity for Node \( packet . from . toHex ( ) , privacy : . public ) " )
}
}
func textMessageAppPacket (
packet : MeshPacket ,
wantRangeTestPackets : Bool ,
critical : Bool = false ,
connectedNode : Int64 ,
storeForward : Bool = false ,
context : NSManagedObjectContext ,
appState : AppState
) {
var messageText = String ( bytes : packet . decoded . payload , encoding : . utf8 )
let rangeRef = Reference ( Int . self )
let rangeTestRegex = Regex {
" seq "
TryCapture ( as : rangeRef ) {
OneOrMore ( . digit )
} transform : { match in
Int ( match )
}
}
let rangeTest = messageText ? . contains ( rangeTestRegex ) ? ? false && messageText ? . starts ( with : " seq " ) ? ? false
if ! wantRangeTestPackets && rangeTest {
return
}
var storeForwardBroadcast = false
if storeForward {
if let storeAndForwardMessage = try ? StoreAndForward ( serializedBytes : packet . decoded . payload ) {
messageText = String ( bytes : storeAndForwardMessage . text , encoding : . utf8 )
if storeAndForwardMessage . rr = = . routerTextBroadcast {
storeForwardBroadcast = true
}
}
}
if messageText ? . count ? ? 0 > 0 {
MeshLogger . log ( " 💬 \( " mesh.log.textmessage.received " . localized ) " )
let messageUsers = UserEntity . fetchRequest ( )
messageUsers . predicate = NSPredicate ( format : " num IN %@ " , [ packet . to , packet . from ] )
do {
let fetchedUsers = try context . fetch ( messageUsers )
let newMessage = MessageEntity ( context : context )
newMessage . messageId = Int64 ( packet . id )
if packet . rxTime > 0 {
newMessage . messageTimestamp = Int32 ( bitPattern : packet . rxTime )
} else {
newMessage . messageTimestamp = Int32 ( Date ( ) . timeIntervalSince1970 )
}
newMessage . receivedACK = false
newMessage . snr = packet . rxSnr
newMessage . rssi = packet . rxRssi
newMessage . isEmoji = packet . decoded . emoji = = 1
newMessage . channel = Int32 ( packet . channel )
newMessage . portNum = Int32 ( packet . decoded . portnum . rawValue )
if packet . decoded . portnum = = PortNum . detectionSensorApp {
if ! UserDefaults . enableDetectionNotifications {
newMessage . read = true
}
}
if packet . decoded . replyID > 0 {
newMessage . replyID = Int64 ( packet . decoded . replyID )
}
if fetchedUsers . first ( where : { $0 . num = = packet . to } ) != nil && packet . to != Constants . maximumNodeNum {
if ! storeForwardBroadcast {
newMessage . toUser = fetchedUsers . first ( where : { $0 . num = = packet . to } )
} else {
// / M a k e a n e w t o u s e r i f t h e y a r e u n k n o w n
newMessage . toUser = createUser ( num : Int64 ( truncatingIfNeeded : packet . to ) , context : context )
}
}
if fetchedUsers . first ( where : { $0 . num = = packet . from } ) != nil {
newMessage . fromUser = fetchedUsers . first ( where : { $0 . num = = packet . from } )
// / S e t t h e p u b l i c k e y f o r t h e m e s s a g e
if newMessage . fromUser ? . pkiEncrypted ? ? false && packet . pkiEncrypted {
newMessage . pkiEncrypted = true
newMessage . publicKey = packet . publicKey
}
// / C h e c k f o r k e y m i s m a t c h
if let nodeKey = newMessage . fromUser ? . publicKey {
if newMessage . toUser != nil && packet . pkiEncrypted && ! packet . publicKey . isEmpty {
if nodeKey != newMessage . publicKey {
newMessage . fromUser ? . keyMatch = false
newMessage . fromUser ? . newPublicKey = newMessage . publicKey
let nodeKey = String ( nodeKey . base64EncodedString ( ) ) . prefix ( 8 )
let messageKey = String ( newMessage . publicKey ? . base64EncodedString ( ) ? ? " No Key " ) . prefix ( 8 )
Logger . data . error ( " 🔑 Key mismatch original key: \( nodeKey , privacy : . public ) . . . new key: \( messageKey , privacy : . public ) . . . " )
}
}
} else if packet . pkiEncrypted {
// / W e h a v e n o k e y , s e t i t i f i t i s n o t e m p t y
if ! packet . publicKey . isEmpty {
newMessage . fromUser ? . pkiEncrypted = true
newMessage . fromUser ? . publicKey = packet . publicKey
}
}
} else {
// / M a k e a n e w f r o m u s e r i f t h e y a r e u n k n o w n
newMessage . fromUser = createUser ( num : Int64 ( truncatingIfNeeded : packet . from ) , context : context )
}
if packet . rxTime > 0 {
newMessage . fromUser ? . userNode ? . lastHeard = Date ( timeIntervalSince1970 : TimeInterval ( Int64 ( packet . rxTime ) ) )
} else {
newMessage . fromUser ? . userNode ? . lastHeard = Date ( )
}
newMessage . messagePayload = messageText
newMessage . messagePayloadMarkdown = generateMessageMarkdown ( message : messageText ! )
if packet . to != Constants . maximumNodeNum && newMessage . fromUser != nil {
newMessage . fromUser ? . lastMessage = Date ( )
}
var messageSaved = false
do {
try context . save ( )
Logger . data . info ( " 💾 Saved a new message for \( newMessage . messageId ) " )
messageSaved = true
if messageSaved {
if packet . decoded . portnum = = PortNum . detectionSensorApp && ! UserDefaults . enableDetectionNotifications {
return
}
if newMessage . fromUser != nil && newMessage . toUser != nil {
// S e t U n r e a d M e s s a g e I n d i c a t o r s
if packet . to = = connectedNode {
appState . unreadDirectMessages = newMessage . toUser ? . unreadMessages ? ? 0
}
if ! ( newMessage . fromUser ? . mute ? ? false ) {
// C r e a t e a n i O S N o t i f i c a t i o n f o r t h e r e c e i v e d D M m e s s a g e a n d s c h e d u l e i t i m m e d i a t e l y
let manager = LocalNotificationManager ( )
manager . notifications = [
Notification (
id : ( " notification.id. \( newMessage . messageId ) " ) ,
title : " \( newMessage . fromUser ? . longName ? ? " unknown " . localized ) " ,
subtitle : " AKA \( newMessage . fromUser ? . shortName ? ? " ? " ) " ,
content : messageText ! ,
target : " messages " ,
path : " meshtastic:///messages?userNum= \( newMessage . fromUser ? . num ? ? 0 ) &messageId= \( newMessage . messageId ) " ,
messageId : newMessage . messageId ,
channel : newMessage . channel ,
userNum : Int64 ( packet . from ) ,
critical : critical
)
]
manager . schedule ( )
Logger . services . debug ( " iOS Notification Scheduled for text message from \( newMessage . fromUser ? . longName ? ? " unknown " . localized ) " )
}
} else if newMessage . fromUser != nil && newMessage . toUser = = nil {
let fetchMyInfoRequest = MyInfoEntity . fetchRequest ( )
fetchMyInfoRequest . predicate = NSPredicate ( format : " myNodeNum == %lld " , Int64 ( connectedNode ) )
do {
let fetchedMyInfo = try context . fetch ( fetchMyInfoRequest )
if ! fetchedMyInfo . isEmpty {
appState . unreadChannelMessages = fetchedMyInfo [ 0 ] . unreadMessages
for channel in ( fetchedMyInfo [ 0 ] . channels ? . array ? ? [ ] ) as ? [ ChannelEntity ] ? ? [ ] {
if channel . index = = newMessage . channel {
context . refresh ( channel , mergeChanges : true )
}
if channel . index = = newMessage . channel && ! channel . mute && UserDefaults . channelMessageNotifications {
// C r e a t e a n i O S N o t i f i c a t i o n f o r t h e r e c e i v e d p r i v a t e c h a n n e l m e s s a g e a n d s c h e d u l e i t i m m e d i a t e l y
let manager = LocalNotificationManager ( )
manager . notifications = [
Notification (
id : ( " notification.id. \( newMessage . messageId ) " ) ,
title : " \( newMessage . fromUser ? . longName ? ? " unknown " . localized ) " ,
subtitle : " AKA \( newMessage . fromUser ? . shortName ? ? " ? " ) " ,
content : messageText ! ,
target : " messages " ,
path : " meshtastic:///messages?channelId= \( newMessage . channel ) &messageId= \( newMessage . messageId ) " ,
messageId : newMessage . messageId ,
channel : newMessage . channel ,
userNum : Int64 ( newMessage . fromUser ? . userId ? ? " 0 " ) ,
critical : critical )
]
manager . schedule ( )
Logger . services . debug ( " iOS Notification Scheduled for text message from \( newMessage . fromUser ? . longName ? ? " unknown " . localized ) " )
}
}
}
} catch {
// H a n d l e e r r o r
}
}
}
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " Failed to save new MessageEntity \( nsError ) " )
}
} catch {
Logger . data . error ( " Fetch Message To and From Users Error " )
}
}
}
func waypointPacket ( packet : MeshPacket , context : NSManagedObjectContext ) {
let logString = String . localizedStringWithFormat ( " mesh.log.waypoint.received %@ " . localized , String ( packet . from ) )
MeshLogger . log ( " 📍 \( logString ) " )
let fetchWaypointRequest = WaypointEntity . fetchRequest ( )
fetchWaypointRequest . predicate = NSPredicate ( format : " id == %lld " , Int64 ( packet . id ) )
do {
if let waypointMessage = try ? Waypoint ( serializedBytes : packet . decoded . payload ) {
let fetchedWaypoint = try context . fetch ( fetchWaypointRequest )
if fetchedWaypoint . isEmpty {
let waypoint = WaypointEntity ( context : context )
waypoint . id = Int64 ( packet . id )
waypoint . name = waypointMessage . name
waypoint . longDescription = waypointMessage . description_p
waypoint . latitudeI = waypointMessage . latitudeI
waypoint . longitudeI = waypointMessage . longitudeI
waypoint . icon = Int64 ( waypointMessage . icon )
waypoint . locked = Int64 ( waypointMessage . lockedTo )
if waypointMessage . expire >= 1 {
waypoint . expire = Date ( timeIntervalSince1970 : TimeInterval ( Int64 ( waypointMessage . expire ) ) )
} else {
waypoint . expire = nil
}
waypoint . created = Date ( )
do {
try context . save ( )
Logger . data . info ( " 💾 Added Node Waypoint App Packet For: \( waypoint . id ) " )
let manager = LocalNotificationManager ( )
let icon = String ( UnicodeScalar ( Int ( waypoint . icon ) ) ? ? " 📍 " )
let latitude = Double ( waypoint . latitudeI ) / 1e7
let longitude = Double ( waypoint . longitudeI ) / 1e7
manager . notifications = [
Notification (
id : ( " notification.id. \( waypoint . id ) " ) ,
title : " New Waypoint Received " ,
subtitle : " \( icon ) \( waypoint . name ? ? " Dropped Pin " ) " ,
content : " \( waypoint . longDescription ? ? " \( latitude ) , \( longitude ) " ) " ,
target : " map " ,
path : " meshtastic:///map?waypointid= \( waypoint . id ) "
)
]
Logger . data . debug ( " meshtastic:///map?waypointid= \( waypoint . id ) " )
manager . schedule ( )
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " Error Saving WaypointEntity from WAYPOINT_APP \( nsError ) " )
}
} else {
fetchedWaypoint [ 0 ] . id = Int64 ( packet . id )
fetchedWaypoint [ 0 ] . name = waypointMessage . name
fetchedWaypoint [ 0 ] . longDescription = waypointMessage . description_p
fetchedWaypoint [ 0 ] . latitudeI = waypointMessage . latitudeI
fetchedWaypoint [ 0 ] . longitudeI = waypointMessage . longitudeI
fetchedWaypoint [ 0 ] . icon = Int64 ( waypointMessage . icon )
fetchedWaypoint [ 0 ] . locked = Int64 ( waypointMessage . lockedTo )
if waypointMessage . expire >= 1 {
fetchedWaypoint [ 0 ] . expire = Date ( timeIntervalSince1970 : TimeInterval ( Int64 ( waypointMessage . expire ) ) )
} else {
fetchedWaypoint [ 0 ] . expire = nil
}
fetchedWaypoint [ 0 ] . lastUpdated = Date ( )
do {
try context . save ( )
Logger . data . info ( " 💾 Updated Node Waypoint App Packet For: \( fetchedWaypoint [ 0 ] . id ) " )
} catch {
context . rollback ( )
let nsError = error as NSError
Logger . data . error ( " Error Saving WaypointEntity from WAYPOINT_APP \( nsError ) " )
}
}
}
} catch {
Logger . mesh . error ( " Error Deserializing WAYPOINT_APP packet. " )
}
2021-10-06 22:24:45 -07:00
}