diff --git a/Meshtastic/Enums/DisplayEnums.swift b/Meshtastic/Enums/DisplayEnums.swift index 46110532..a65bd94b 100644 --- a/Meshtastic/Enums/DisplayEnums.swift +++ b/Meshtastic/Enums/DisplayEnums.swift @@ -104,6 +104,7 @@ enum OledTypes: Int, CaseIterable, Identifiable { case auto = 0 case ssd1306 = 1 case sh1106 = 2 + case sh1107 = 3 var id: Int { self.rawValue } var description: String { @@ -115,6 +116,8 @@ enum OledTypes: Int, CaseIterable, Identifiable { return "SSD 1306" case .sh1106: return "SH 1106" + case .sh1107: + return "SH 1107" } } } @@ -127,6 +130,46 @@ enum OledTypes: Int, CaseIterable, Identifiable { return Config.DisplayConfig.OledType.oledSsd1306 case .sh1106: return Config.DisplayConfig.OledType.oledSh1106 + case .sh1107: + return Config.DisplayConfig.OledType.oledSh1106 + } + } +} + +// Default of 0 is auto +enum DisplayModes: Int, CaseIterable, Identifiable { + + case defaultMode = 0 + case twoColor = 1 + case inverted = 2 + case color = 3 + + var id: Int { self.rawValue } + var description: String { + get { + switch self { + case .defaultMode: + return "Default 128x64 screen layout" + case .twoColor: + return "Optimized for 2 color displays" + case .inverted: + return "Inverted top bar for 2 Color display" + case .color: + return "TFT Full Color Displays" + } + } + } + func protoEnumValue() -> Config.DisplayConfig.DisplayMode { + + switch self { + case .defaultMode: + return Config.DisplayConfig.DisplayMode.default + case .twoColor: + return Config.DisplayConfig.DisplayMode.twocolor + case .inverted: + return Config.DisplayConfig.DisplayMode.inverted + case .color: + return Config.DisplayConfig.DisplayMode.color } } } diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 808b9bd7..1ad90961 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -505,7 +505,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { case .remoteHardwareApp: MeshLogger.log("πŸ•ΈοΈ MESH PACKET received for Remote Hardware App UNHANDLED \(try! decodedInfo.packet.jsonString())") case .positionApp: - positionPacket(packet: decodedInfo.packet, context: context!) + upsertPositionPacket(packet: decodedInfo.packet, context: context!) case .waypointApp: waypointPacket(packet: decodedInfo.packet, context: context!) case .nodeinfoApp: diff --git a/Meshtastic/Helpers/MeshPackets.swift b/Meshtastic/Helpers/MeshPackets.swift index ae101ad9..cbd9a56c 100644 --- a/Meshtastic/Helpers/MeshPackets.swift +++ b/Meshtastic/Helpers/MeshPackets.swift @@ -589,7 +589,8 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje newNode.user = newUser } - if nodeInfo.position.latitudeI > 0 || nodeInfo.position.longitudeI > 0 { + if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) + { let position = PositionEntity(context: context) position.seqNo = Int32(nodeInfo.position.seqNumber) position.latitudeI = nodeInfo.position.latitudeI @@ -656,14 +657,19 @@ func nodeInfoPacket (nodeInfo: NodeInfo, channel: UInt32, context: NSManagedObje if nodeInfo.hasPosition { - 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))) - let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet - fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet + + if nodeInfo.position.longitudeI > 0 || nodeInfo.position.latitudeI > 0 && (nodeInfo.position.latitudeI != 373346000 && nodeInfo.position.longitudeI != -1220090000) { + + 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))) + let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet + fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet + } + } // Look for a MyInfo @@ -753,6 +759,8 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { if let adminMessage = try? AdminMessage(serializedData: packet.decoded.payload) { + + if adminMessage.payloadVariant == AdminMessage.OneOf_PayloadVariant.getCannedMessageModuleMessagesResponse(adminMessage.getCannedMessageModuleMessagesResponse) { if let cmmc = try? CannedMessageModuleConfig(serializedData: packet.decoded.payload) { @@ -819,62 +827,6 @@ func adminAppPacket (packet: MeshPacket, context: NSManagedObjectContext) { } } -func positionPacket (packet: MeshPacket, context: NSManagedObjectContext) { - - let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.position.received %@", comment: "Position Packet received from node: %@"), String(packet.from)) - MeshLogger.log("πŸ“ \(logString)") - - let fetchNodePositionRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") - fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) - - do { - - if let positionMessage = try? Position(serializedData: packet.decoded.payload) { - // Don't save empty position packets - if positionMessage.longitudeI > 0 || positionMessage.latitudeI > 0 { - let fetchedNode = try context.fetch(fetchNodePositionRequest) as! [NodeInfoEntity] - if fetchedNode.count == 1 { - - let position = PositionEntity(context: context) - position.snr = packet.rxSnr - position.seqNo = Int32(positionMessage.seqNumber) - position.latitudeI = positionMessage.latitudeI - position.longitudeI = positionMessage.longitudeI - position.altitude = positionMessage.altitude - position.satsInView = Int32(positionMessage.satsInView) - position.speed = Int32(positionMessage.groundSpeed) - position.heading = Int32(positionMessage.groundTrack) - if positionMessage.timestamp != 0 { - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.timestamp))) - } else { - position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) - } - let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet - mutablePositions.add(position) - fetchedNode[0].id = Int64(packet.from) - fetchedNode[0].num = Int64(packet.from) - fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) - fetchedNode[0].snr = packet.rxSnr - fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet - do { - try context.save() - print("πŸ’Ύ Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") - } catch { - context.rollback() - let nsError = error as NSError - print("πŸ’₯ Error Saving NodeInfoEntity from POSITION_APP \(nsError)") - } - } - } else { - print("πŸ’₯ Empty POSITION_APP Packet") - print(try! packet.jsonString()) - } - } - } catch { - print("πŸ’₯ Error Deserializing POSITION_APP packet.") - } -} - func routingPacket (packet: MeshPacket, connectedNodeNum: Int64, context: NSManagedObjectContext) { if let routingMessage = try? Routing(serializedData: packet.decoded.payload) { diff --git a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV6.xcdatamodel/contents b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV6.xcdatamodel/contents index 5602f840..6f82e5d0 100644 --- a/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV6.xcdatamodel/contents +++ b/Meshtastic/Meshtastic.xcdatamodeld/MeshtasticDataModelV6.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -60,6 +60,7 @@ + diff --git a/Meshtastic/Persistence/UpdateCoreData.swift b/Meshtastic/Persistence/UpdateCoreData.swift index c739ecde..6552e54a 100644 --- a/Meshtastic/Persistence/UpdateCoreData.swift +++ b/Meshtastic/Persistence/UpdateCoreData.swift @@ -62,7 +62,7 @@ public func clearTelemetry(destNum: Int64, metricsType: Int32, context: NSManage public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObjectContext) { do { - let objects = channel.allPrivateMessages// try context.fetch(fetchChannelMessagesRequest) as! [NSManagedObject] + let objects = channel.allPrivateMessages for object in objects { context.delete(object) } @@ -75,7 +75,7 @@ public func deleteChannelMessages(channel: ChannelEntity, context: NSManagedObje public func deleteUserMessages(user: UserEntity, context: NSManagedObjectContext) { do { - let objects = user.messageList//try context.fetch(fetchUserMessagesRequest) as! [NSManagedObject] + let objects = user.messageList for object in objects { context.delete(object) } @@ -101,6 +101,64 @@ public func clearCoreDataDatabase(context: NSManagedObjectContext) { } } +func upsertPositionPacket (packet: MeshPacket, context: NSManagedObjectContext) { + + let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.position.received %@", comment: "Position Packet received from node: %@"), String(packet.from)) + MeshLogger.log("πŸ“ \(logString)") + + let fetchNodePositionRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from)) + + do { + + if let positionMessage = try? Position(serializedData: packet.decoded.payload) { + + // Don't save empty position packets + if positionMessage.longitudeI > 0 || positionMessage.latitudeI > 0 && (positionMessage.latitudeI != 373346000 && positionMessage.longitudeI != -1220090000) + { + let fetchedNode = try context.fetch(fetchNodePositionRequest) as! [NodeInfoEntity] + if fetchedNode.count == 1 { + + let position = PositionEntity(context: context) + position.snr = packet.rxSnr + position.seqNo = Int32(positionMessage.seqNumber) + position.latitudeI = positionMessage.latitudeI + position.longitudeI = positionMessage.longitudeI + position.altitude = positionMessage.altitude + position.satsInView = Int32(positionMessage.satsInView) + position.speed = Int32(positionMessage.groundSpeed) + position.heading = Int32(positionMessage.groundTrack) + if positionMessage.timestamp != 0 { + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.timestamp))) + } else { + position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + } + let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet + mutablePositions.add(position) + fetchedNode[0].id = Int64(packet.from) + fetchedNode[0].num = Int64(packet.from) + fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time))) + fetchedNode[0].snr = packet.rxSnr + fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet + do { + try context.save() + print("πŸ’Ύ Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)") + } catch { + context.rollback() + let nsError = error as NSError + print("πŸ’₯ Error Saving NodeInfoEntity from POSITION_APP \(nsError)") + } + } + } else { + print("πŸ’₯ Empty POSITION_APP Packet") + print(try! packet.jsonString()) + } + } + } catch { + print("πŸ’₯ Error Deserializing POSITION_APP packet.") + } +} + func upsertBluetoothConfigPacket(config: Config, nodeNum: Int64, context: NSManagedObjectContext) { let logString = String.localizedStringWithFormat(NSLocalizedString("mesh.log.bluetooth.config %@", comment: "Bluetooth config received: %@"), String(nodeNum)) @@ -208,6 +266,7 @@ func upsertDisplayConfigPacket(config: Config, nodeNum: Int64, context: NSManage newDisplayConfig.compassNorthTop = config.display.compassNorthTop newDisplayConfig.flipScreen = config.display.flipScreen newDisplayConfig.oledType = Int32(config.display.oled.rawValue) + newDisplayConfig.displayMode = Int32(config.display.displaymode.rawValue) fetchedNode[0].displayConfig = newDisplayConfig } else { @@ -218,6 +277,7 @@ func upsertDisplayConfigPacket(config: Config, nodeNum: Int64, context: NSManage fetchedNode[0].displayConfig?.compassNorthTop = config.display.compassNorthTop fetchedNode[0].displayConfig?.flipScreen = config.display.flipScreen fetchedNode[0].displayConfig?.oledType = Int32(config.display.oled.rawValue) + fetchedNode[0].displayConfig?.displayMode = Int32(config.display.displaymode.rawValue) } do { diff --git a/Meshtastic/Views/Settings/Config/DisplayConfig.swift b/Meshtastic/Views/Settings/Config/DisplayConfig.swift index 9103c5ce..1554b536 100644 --- a/Meshtastic/Views/Settings/Config/DisplayConfig.swift +++ b/Meshtastic/Views/Settings/Config/DisplayConfig.swift @@ -24,29 +24,20 @@ struct DisplayConfig: View { @State var compassNorthTop = false @State var flipScreen = false @State var oledType = 0 + @State var displayMode = 0 var body: some View { Form { Section(header: Text("Device Screen")) { - Picker("Screen on for", selection: $screenOnSeconds ) { - ForEach(ScreenOnIntervals.allCases) { soi in - Text(soi.description) + Picker("Display Mode", selection: $displayMode ) { + ForEach(DisplayModes.allCases) { dm in + Text(dm.description) } } .pickerStyle(DefaultPickerStyle()) - - Text("How long the screen remains on after the user button is pressed or messages are received.") - .font(.caption) - - Picker("Carousel Interval", selection: $screenCarouselInterval ) { - ForEach(ScreenCarouselIntervals.allCases) { sci in - Text(sci.description) - } - } - .pickerStyle(DefaultPickerStyle()) - Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.") + Text("Override automatic OLED screen detection.") .font(.caption) Toggle(isOn: $compassNorthTop) { @@ -64,6 +55,7 @@ struct DisplayConfig: View { .toggleStyle(SwitchToggleStyle(tint: .accentColor)) Text("Flip screen vertically") .font(.caption) + Picker("OLED Type", selection: $oledType ) { ForEach(OledTypes.allCases) { ot in Text(ot.description) @@ -74,7 +66,25 @@ struct DisplayConfig: View { .font(.caption) } - Section(header: Text("Format")) { + Section(header: Text("Timing & Format")) { + Picker("Screen on for", selection: $screenOnSeconds ) { + ForEach(ScreenOnIntervals.allCases) { soi in + Text(soi.description) + } + } + .pickerStyle(DefaultPickerStyle()) + Text("How long the screen remains on after the user button is pressed or messages are received.") + .font(.caption) + + Picker("Carousel Interval", selection: $screenCarouselInterval ) { + ForEach(ScreenCarouselIntervals.allCases) { sci in + Text(sci.description) + } + } + .pickerStyle(DefaultPickerStyle()) + Text("Automatically toggles to the next page on the screen like a carousel, based the specified interval.") + .font(.caption) + Picker("GPS Format", selection: $gpsFormat ) { ForEach(GpsFormats.allCases) { lu in Text(lu.description) @@ -116,6 +126,7 @@ struct DisplayConfig: View { dc.compassNorthTop = compassNorthTop dc.flipScreen = flipScreen dc.oled = OledTypes(rawValue: oledType)!.protoEnumValue() + dc.displaymode = DisplayModes(rawValue: displayMode)!.protoEnumValue() let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node!.user!, toUser: node!.user!) if adminMessageId > 0 { @@ -143,6 +154,7 @@ struct DisplayConfig: View { self.compassNorthTop = node?.displayConfig?.compassNorthTop ?? false self.flipScreen = node?.displayConfig?.flipScreen ?? false self.oledType = Int(node?.displayConfig?.oledType ?? 0) + self.displayMode = Int(node?.displayConfig?.displayMode ?? 0) self.hasChanges = false } .onChange(of: screenOnSeconds) { newScreenSecs in @@ -175,5 +187,10 @@ struct DisplayConfig: View { if newOledType != node!.displayConfig!.oledType { hasChanges = true } } } + .onChange(of: displayMode) { newDisplayMode in + if node != nil && node!.displayConfig != nil { + if newDisplayMode != node!.displayConfig!.displayMode { hasChanges = true } + } + } } }