Merge pull request #115 from meshtastic/feature/module_settings

Handle empty positions, allow nrf52 saving for telemetry
This commit is contained in:
Garth Vander Houwen 2022-07-07 00:31:20 -07:00 committed by GitHub
commit 9f5565fed3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 865 additions and 674 deletions

View file

@ -39,6 +39,7 @@
DD6193772862F90F00E59241 /* CannedMessagesConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */; };
DD6193792863875F00E59241 /* SerialConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6193782863875F00E59241 /* SerialConfig.swift */; };
DD6B85A828009258000ACD6B /* ShareChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B85A728009258000ACD6B /* ShareChannel.swift */; };
DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FD1028750779000852D6 /* LocationHistory.swift */; };
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */; };
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; };
DD8169FF272476C700F4AB02 /* LogDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FE272476C700F4AB02 /* LogDocument.swift */; };
@ -127,6 +128,7 @@
DD6193762862F90F00E59241 /* CannedMessagesConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CannedMessagesConfig.swift; sourceTree = "<group>"; };
DD6193782863875F00E59241 /* SerialConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialConfig.swift; sourceTree = "<group>"; };
DD6B85A728009258000ACD6B /* ShareChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareChannel.swift; sourceTree = "<group>"; };
DD73FD1028750779000852D6 /* LocationHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationHistory.swift; sourceTree = "<group>"; };
DD8169F8271F1A6100F4AB02 /* MeshLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLogger.swift; sourceTree = "<group>"; };
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
DD8169FE272476C700F4AB02 /* LogDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogDocument.swift; sourceTree = "<group>"; };
@ -225,6 +227,7 @@
DD47E3CD26F103C600029299 /* NodeList.swift */,
DD90860D26F69BAE00DC5189 /* NodeMap.swift */,
DD2E65252767A01F00E45FC5 /* NodeDetail.swift */,
DD73FD1028750779000852D6 /* LocationHistory.swift */,
);
path = Nodes;
sourceTree = "<group>";
@ -661,6 +664,7 @@
DD4C158E2824AA7E0032668E /* config.pb.swift in Sources */,
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */,
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
DD73FD1128750779000852D6 /* LocationHistory.swift in Sources */,
C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */,
DD0F791B28713C8A00A6FDAD /* AdminMessageList.swift in Sources */,
DD8169F9271F1A6100F4AB02 /* MeshLogger.swift in Sources */,

View file

@ -660,7 +660,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessage.messageId = Int64(UInt32.random(in: UInt32(UInt8.max)..<UInt32.max))
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "IN"
newMessage.toUser = fetchedUsers.first(where: { $0.num == toUserNum })
newMessage.isEmoji = isEmoji
newMessage.admin = false
@ -831,8 +830,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = wantResponse
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
@ -848,10 +845,23 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
do {
try context!.save()
if meshLoggingEnabled { MeshLogger.log("💾 Saved a Shutdown Admin Message for node: \(String(destNum))") }
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
} catch {
context!.rollback()
let nsError = error as NSError
print("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
}
}
return false
@ -884,9 +894,23 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
do {
try context!.save()
if meshLoggingEnabled { MeshLogger.log("💾 Saved a Reboot Admin Message for node: \(String(destNum))") }
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
} catch {
context!.rollback()
let nsError = error as NSError
print("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
}
}
return false
@ -906,7 +930,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
meshPacket.id = UInt32.random(in: UInt32(UInt8.max)..<UInt32.max)
meshPacket.priority = MeshPacket.Priority.reliable
meshPacket.wantAck = wantResponse
meshPacket.hopLimit = 0
var dataMessage = DataMessage()
dataMessage.payload = try! adminPacket.serializedData()
@ -922,15 +945,28 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if connectedPeripheral!.peripheral.state == CBPeripheralState.connected {
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
do {
try context!.save()
if meshLoggingEnabled { MeshLogger.log("💾 Saved a Factory Reset Admin Message for node: \(String(destNum))") }
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
return true
} catch {
context!.rollback()
let nsError = error as NSError
print("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
}
}
return false
}
public func saveUser(config: User, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 {
public func saveUser(config: User, fromUser: UserEntity?, toUser: UserEntity?, wantResponse: Bool) -> Int64 {
var newMessageId: Int64 = 0
@ -964,18 +1000,17 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved User Config for \(toUser.longName ?? "Unknown")"
newMessage.adminDescription = "Saved User Config for \(toUser!.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
try context!.save()
if meshLoggingEnabled { MeshLogger.log("💾 Saved a new User Config Admin Message for node: \(String(toUser.num))") }
if meshLoggingEnabled { MeshLogger.log("💾 Saved a new User Config Admin Message for node: \(String(toUser!.num))") }
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
} catch {
@ -1024,12 +1059,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved Device Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1083,12 +1117,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved Display Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1143,12 +1176,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "IN"
newMessage.admin = true
newMessage.adminDescription = "Saved LoRa Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1202,12 +1234,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved Position Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1261,12 +1292,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved Canned Message Module Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1319,12 +1349,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved External Notification Module Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1341,9 +1370,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
print("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
}
}
return newMessageId
}
public func saveRangeTestModuleConfig(config: ModuleConfig.RangeTestConfig, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 {
@ -1379,12 +1406,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved Range Test Module Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1401,9 +1427,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
print("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
}
}
return newMessageId
}
public func saveSerialModuleConfig(config: ModuleConfig.SerialConfig, fromUser: UserEntity, toUser: UserEntity, wantResponse: Bool) -> Int64 {
@ -1440,12 +1464,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved Serial Module Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1462,7 +1485,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
print("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
}
}
return newMessageId
}
@ -1499,12 +1521,11 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
newMessageId = newMessage.messageId
newMessage.messageTimestamp = Int32(Date().timeIntervalSince1970)
newMessage.receivedACK = false
newMessage.direction = "OUT"
newMessage.admin = true
newMessage.adminDescription = "Saved Telemetry Module Config for \(toUser.longName ?? "Unknown")"
newMessage.fromUser = fromUser
newMessage.toUser = toUser
newMessage.messagePayload = try! dataMessage.jsonString()
newMessage.messagePayload = try! config.jsonString()
do {
@ -1521,8 +1542,6 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
print("💥 Error Inserting New Core Data MessageEntity: \(nsError)")
}
}
return newMessageId
}
}

View file

@ -896,15 +896,18 @@ func nodeInfoPacket (nodeInfo: NodeInfo, meshLogging: Bool, context: NSManagedOb
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)
if nodeInfo.position.latitudeI > 0 || nodeInfo.position.longitudeI > 0 {
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")
@ -1132,54 +1135,59 @@ func positionPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedOb
let fetchNodePositionRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "NodeInfoEntity")
fetchNodePositionRequest.predicate = NSPredicate(format: "num == %lld", Int64(packet.from))
do {
let fetchedNode = try context.fetch(fetchNodePositionRequest) as! [NodeInfoEntity]
if fetchedNode.count == 1 {
fetchedNode[0].id = Int64(packet.from)
fetchedNode[0].num = Int64(packet.from)
fetchedNode[0].lastHeard = Date(timeIntervalSince1970: TimeInterval(Int64(packet.rxTime)))
fetchedNode[0].snr = packet.rxSnr
if let positionMessage = try? Position(serializedData: packet.decoded.payload) {
let position = PositionEntity(context: context)
position.latitudeI = positionMessage.latitudeI
position.longitudeI = positionMessage.longitudeI
position.altitude = positionMessage.altitude
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time)))
let mutablePositions = fetchedNode[0].positions!.mutableCopy() as! NSMutableOrderedSet
mutablePositions.add(position)
fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
}
}
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]
try context.save()
if fetchedNode.count == 1 {
let position = PositionEntity(context: context)
position.latitudeI = positionMessage.latitudeI
position.longitudeI = positionMessage.longitudeI
position.altitude = positionMessage.altitude
position.time = Date(timeIntervalSince1970: TimeInterval(Int64(positionMessage.time)))
if meshLogging {
MeshLogger.log("💾 Updated Node Position Coordinates, SNR and Time from Position App Packet For: \(fetchedNode[0].num)")
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(packet.rxTime)))
fetchedNode[0].snr = packet.rxSnr
fetchedNode[0].positions = mutablePositions.copy() as? NSOrderedSet
do {
try context.save()
if meshLogging {
MeshLogger.log("💾 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")
}
} catch {
context.rollback()
let nsError = error as NSError
print("💥 Error Saving NodeInfoEntity from POSITION_APP \(nsError)")
}
} catch {
print("💥 Error Fetching NodeInfoEntity for POSITION_APP")
}
}
func routingPacket (packet: MeshPacket, meshLogging: Bool, context: NSManagedObjectContext) {
@ -1332,7 +1340,6 @@ func textMessageAppPacket(packet: MeshPacket, connectedNode: Int64, meshLogging:
newMessage.messageId = Int64(packet.id)
newMessage.messageTimestamp = Int32(bitPattern: packet.rxTime)
newMessage.receivedACK = false
newMessage.direction = "IN"
newMessage.isEmoji = packet.decoded.emoji == 1
if packet.decoded.replyID > 0 {

View file

@ -50,7 +50,6 @@
<attribute name="ackTimestamp" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="admin" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="adminDescription" optional="YES" attributeType="String"/>
<attribute name="direction" attributeType="String"/>
<attribute name="isEmoji" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="messageId" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="messagePayload" attributeType="String"/>
@ -195,17 +194,17 @@
<element name="CannedMessageConfigEntity" positionX="45" positionY="144" width="128" height="209"/>
<element name="DeviceConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
<element name="DisplayConfigEntity" positionX="54" positionY="153" width="128" height="104"/>
<element name="ExternalNotificationConfigEntity" positionX="63" positionY="162" width="128" height="149"/>
<element name="LoRaConfigEntity" positionX="45" positionY="144" width="128" height="104"/>
<element name="MessageEntity" positionX="-36" positionY="63" width="128" height="245"/>
<element name="MessageEntity" positionX="-36" positionY="63" width="128" height="230"/>
<element name="MyInfoEntity" positionX="-18" positionY="81" width="128" height="209"/>
<element name="NodeInfoEntity" positionX="-63" positionY="-18" width="128" height="299"/>
<element name="PositionConfigEntity" positionX="63" positionY="162" width="128" height="149"/>
<element name="PositionEntity" positionX="-54" positionY="54" width="128" height="119"/>
<element name="RangeTestConfigEntity" positionX="72" positionY="171" width="128" height="104"/>
<element name="SerialConfigEntity" positionX="54" positionY="153" width="128" height="164"/>
<element name="TelemetryConfigEntity" positionX="72" positionY="171" width="128" height="179"/>
<element name="TelemetryEntity" positionX="160" positionY="192" width="128" height="194"/>
<element name="UserEntity" positionX="0" positionY="144" width="128" height="215"/>
<element name="SerialConfigEntity" positionX="54" positionY="153" width="128" height="164"/>
<element name="ExternalNotificationConfigEntity" positionX="63" positionY="162" width="128" height="149"/>
<element name="TelemetryConfigEntity" positionX="72" positionY="171" width="128" height="179"/>
</elements>
</model>

View file

@ -1,13 +1,10 @@
//
// Connect.swift
// MeshtasticApple
// Meshtastic Apple
//
// Created by Garth Vander Houwen on 8/18/21.
// Copyright(c) Garth Vander Houwen 8/18/21.
//
// Abstract:
// A view allowing you to interact with nearby meshtastic nodes
import SwiftUI
import MapKit
import CoreLocation
@ -20,7 +17,6 @@ struct Connect: View {
@EnvironmentObject var userSettings: UserSettings
@State var initialLoad: Bool = true
@State var isPreferredRadio: Bool = false
var body: some View {
@ -32,158 +28,167 @@ struct Connect: View {
NavigationView {
VStack {
if bleManager.isSwitchedOn {
List {
List {
if bleManager.isSwitchedOn {
if supportedVersion == false {
if supportedVersion == false {
Section(header: Text("Upgrade your Firmware").font(.title)) {
Section(header: Text("Upgrade your Firmware").font(.title)) {
Text("🚨 1.3 ALPHA PREVIEW this version of the app supports only version \(minimumVersion).").font(.subheadline).foregroundColor(.red)
}
.textCase(nil)
Text("🚨 1.3 ALPHA PREVIEW this version of the app supports only version \(minimumVersion).").font(.subheadline).foregroundColor(.red)
}
if bleManager.lastConnectionError.count > 0 {
.textCase(nil)
}
if bleManager.lastConnectionError.count > 0 {
Section(header: Text("Connection Error").font(.title)) {
Section(header: Text("Connection Error").font(.title)) {
Text(bleManager.lastConnectionError).font(.title3).foregroundColor(.red)
}
.textCase(nil)
Text(bleManager.lastConnectionError).font(.title3).foregroundColor(.red)
}
.textCase(nil)
}
Section(header: Text("Connected Radio").font(.title)) {
Section(header: Text("Connected Radio").font(.title)) {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected {
HStack {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == .connected {
HStack {
Image(systemName: "antenna.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
.imageScale(.large).foregroundColor(.green)
.padding(.trailing)
Image(systemName: "antenna.radiowaves.left.and.right")
.symbolRenderingMode(.hierarchical)
.imageScale(.large).foregroundColor(.green)
.padding(.trailing)
VStack(alignment: .leading) {
VStack(alignment: .leading) {
if bleManager.connectedPeripheral != nil {
if bleManager.connectedPeripheral != nil {
Text(bleManager.connectedPeripheral.longName).font(.title2)
Text(bleManager.connectedPeripheral.longName).font(.title2)
}
Text("BLE Name: ").font(.caption)+Text(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")
.font(.caption).foregroundColor(Color.gray)
if bleManager.connectedPeripheral != nil {
Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.firmwareVersion)
.font(.caption).foregroundColor(Color.gray)
Text("Bitrate: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00))
.font(.caption).foregroundColor(Color.gray)
Text("Channel Utilization: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00))
.font(.caption).foregroundColor(Color.gray)
Text("Air Time: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00))
.font(.caption).foregroundColor(Color.gray)
}
if bleManager.connectedPeripheral.subscribed {
Text("Properly Subscribed").font(.caption)
}
}
Spacer()
Text("BLE Name: ").font(.caption)+Text(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")
.font(.caption).foregroundColor(Color.gray)
if bleManager.connectedPeripheral != nil {
Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.firmwareVersion)
.font(.caption).foregroundColor(Color.gray)
Text("Bitrate: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.bitrate ?? 0.00))
.font(.caption).foregroundColor(Color.gray)
Text("Channel Utilization: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.channelUtilization ?? 0.00))
.font(.caption).foregroundColor(Color.gray)
Text("Air Time: ").font(.caption)+Text(String(format: "%.2f", bleManager.connectedPeripheral.airTime ?? 0.00))
.font(.caption).foregroundColor(Color.gray)
}
if bleManager.connectedPeripheral.subscribed {
Text("Properly Subscribed").font(.caption)
}
}
Spacer()
VStack(alignment: .center) {
VStack(alignment: .center) {
Text("Preferred").font(.caption2)
Text("Radio").font(.caption2)
Toggle("Preferred Radio", isOn: $isPreferredRadio)
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.labelsHidden()
.onChange(of: isPreferredRadio) { value in
if value {
Text("Preferred").font(.caption2)
Text("Radio").font(.caption2)
Toggle("Preferred Radio", isOn: $isPreferredRadio)
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.labelsHidden()
.onChange(of: isPreferredRadio) { value in
if value {
if bleManager.connectedPeripheral != nil {
if bleManager.connectedPeripheral != nil {
let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "")
userSettings.preferredPeripheralName = deviceName
} else {
userSettings.preferredPeripheralName = bleManager.connectedPeripheral.longName
}
userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString
let deviceName = (bleManager.connectedPeripheral.peripheral.name ?? "")
userSettings.preferredPeripheralName = deviceName
} else {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId {
userSettings.preferredPeripheralId = ""
userSettings.preferredPeripheralName = ""
userSettings.preferredPeripheralName = bleManager.connectedPeripheral.longName
}
}
}
}
}
.swipeActions {
Button(role: .destructive) {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
bleManager.disconnectPeripheral()
isPreferredRadio = false
}
} label: {
Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
}
}
.padding([.top, .bottom])
} else {
HStack {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.symbolRenderingMode(.hierarchical)
.imageScale(.large).foregroundColor(.red)
.padding(.trailing)
Text("No device connected").font(.title3)
}
.padding()
}
userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString
}
.textCase(nil)
if bleManager.peripherals.count > 0 {
Section(header: Text("Available Radios").font(.title)) {
ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name < $1.name })) { peripheral in
HStack {
Image(systemName: "circle.fill")
.imageScale(.large).foregroundColor(.gray)
.padding(.trailing)
Button(action: {
self.bleManager.stopScanning()
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
self.bleManager.disconnectPeripheral()
}
self.bleManager.connectTo(peripheral: peripheral.peripheral)
if userSettings.preferredPeripheralId == peripheral.peripheral.identifier.uuidString {
isPreferredRadio = true
} else {
isPreferredRadio = false
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.identifier.uuidString == userSettings.preferredPeripheralId {
userSettings.preferredPeripheralId = ""
userSettings.preferredPeripheralName = ""
}
}) {
Text(peripheral.name).font(.title3)
}
Spacer()
Text(String(peripheral.rssi) + " dB").font(.title3)
}.padding([.bottom, .top])
}
}
}.textCase(nil)
}
.swipeActions {
Button(role: .destructive) {
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
bleManager.disconnectPeripheral()
isPreferredRadio = false
}
} label: {
Label("Disconnect", systemImage: "antenna.radiowaves.left.and.right.slash")
}
}
.padding([.top, .bottom])
} else {
HStack {
Image(systemName: "antenna.radiowaves.left.and.right.slash")
.symbolRenderingMode(.hierarchical)
.imageScale(.large).foregroundColor(.red)
.padding(.trailing)
Text("No device connected").font(.title3)
}
.padding()
}
}
}
.textCase(nil)
if bleManager.peripherals.count > 0 {
Section(header: Text("Available Radios").font(.title)) {
ForEach(bleManager.peripherals.filter({ $0.peripheral.state == CBPeripheralState.disconnected }).sorted(by: { $0.name < $1.name })) { peripheral in
HStack {
Image(systemName: "circle.fill")
.imageScale(.large).foregroundColor(.gray)
.padding(.trailing)
Button(action: {
self.bleManager.stopScanning()
if bleManager.connectedPeripheral != nil && bleManager.connectedPeripheral.peripheral.state == CBPeripheralState.connected {
self.bleManager.disconnectPeripheral()
}
self.bleManager.connectTo(peripheral: peripheral.peripheral)
if userSettings.preferredPeripheralId == peripheral.peripheral.identifier.uuidString {
isPreferredRadio = true
} else {
isPreferredRadio = false
}
}) {
Text(peripheral.name).font(.title3)
}
Spacer()
Text(String(peripheral.rssi) + " dB").font(.title3)
}.padding([.bottom, .top])
}
}.textCase(nil)
}
} else {
Text("Bluetooth: OFF")
.foregroundColor(.red)
.font(.title)
}
}
HStack(alignment: .center) {
@ -250,11 +255,7 @@ struct Connect: View {
}
.padding(.bottom, 10)
} else {
Text("Bluetooth: OFF")
.foregroundColor(.red)
.font(.title)
}
}
.navigationTitle("Bluetooth Radios")
.navigationBarItems(trailing:
@ -272,11 +273,11 @@ struct Connect: View {
.navigationViewStyle(StackNavigationViewStyle())
.onAppear(perform: {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
if initialLoad {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
// Ask for notification permission
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {

View file

@ -21,7 +21,7 @@ struct ContentView: View {
TabView(selection: $selection) {
Contacts()
.tabItem {
Label("Messages", systemImage: "text.bubble")
Label("Messages", systemImage: "message")
.symbolRenderingMode(.hierarchical)
.symbolVariant(.none)

View file

@ -0,0 +1,115 @@
//
// LocationHistory.swift
// Meshtastic
//
// Copyright(c) Garth Vander Houwen 7/5/22.
//
import SwiftUI
struct LocationHistory: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var body: some View {
VStack {
List {
ForEach(node.positions!.array as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in
VStack {
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
HStack {
Image(systemName: "mappin.and.ellipse")
.foregroundColor(.accentColor)
.font(.callout)
Text("Lat/Long:").font(.callout)
Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))")
.foregroundColor(.gray)
.font(.callout)
Image(systemName: "arrow.up.arrow.down.circle")
.font(.callout)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Alt:")
.font(.callout)
Text("\(String(mappin.altitude))m")
.foregroundColor(.gray)
.font(.callout)
Image(systemName: "clock.badge.checkmark.fill")
.font(.subheadline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Time:")
.font(.callout)
DateTimeText(dateTime: mappin.time)
.foregroundColor(.gray)
.font(.callout)
}
} else {
HStack {
Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline)
Text("Lat/Long:").font(.caption)
Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))")
.foregroundColor(.gray)
.font(.caption)
Image(systemName: "arrow.up.arrow.down.circle")
.font(.subheadline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Alt:")
.font(.caption)
Text("\(String(mappin.altitude))m")
.foregroundColor(.gray)
.font(.caption)
}
HStack {
Image(systemName: "clock.badge.checkmark.fill")
.font(.subheadline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Time:")
.font(.caption)
DateTimeText(dateTime: mappin.time)
.foregroundColor(.gray)
.font(.caption)
}
}
}
}
}
}
.padding()
.navigationTitle("Location History \(node.positions?.count ?? 0) Points")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
ZStack {
ConnectedDevice(bluetoothOn: bleManager.isSwitchedOn, deviceConnected: bleManager.connectedPeripheral != nil, name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
})
.onAppear {
self.bleManager.context = context
}
}
}

View file

@ -11,7 +11,8 @@ struct NodeDetail: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@State var initialLoad: Bool = true
@State private var isPresentingShutdownConfirm: Bool = false
@State private var isPresentingRebootConfirm: Bool = false
@ -28,8 +29,8 @@ struct NodeDetail: View {
VStack {
if node.positions?.count ?? 0 >= 1 {
if node.positions?.count ?? 0 > 0 {
let mostRecent = node.positions?.lastObject as! PositionEntity
if mostRecent.coordinate != nil {
@ -51,43 +52,50 @@ struct NodeDetail: View {
interactionModes: [.all],
showsUserLocation: true,
userTrackingMode: .constant(.follow),
annotationItems: annotations
)
annotationItems: annotations)
{ location in
return MapAnnotation(
coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0),
content: {
NodeAnnotation(time: location.time!)
}
coordinate: location.coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0),
content: {
NodeAnnotation(time: location.time!)
}
)
}
.frame(idealWidth: bounds.size.width, maxHeight: bounds.size.height / 2)
.ignoresSafeArea(.all, edges: [.leading, .trailing])
.frame(idealWidth: bounds.size.width, minHeight: bounds.size.height / 2)
}
} else {
NavigationLink {
LocationHistory(node: node)
} label: {
Image(node.user?.hwModel ?? "UNSET")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: bounds.size.width, height: bounds.size.height / 2)
Image(systemName: "building.columns")
.symbolRenderingMode(.hierarchical)
.font(.title)
Text("Position History \(node.positions?.count ?? 0) Points")
.font(.title2)
}
}
} else {
Image(node.user?.hwModel ?? "UNSET")
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
.frame(width: bounds.size.width, height: bounds.size.height / 2)
}
ScrollView {
HStack {
if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num {
if self.bleManager.connectedPeripheral != nil && self.bleManager.connectedPeripheral.num == node.num && self.bleManager.connectedPeripheral.num == node.num {
HStack {
if hwModelString == "TBEAM" || hwModelString == "TECHO" || hwModelString.contains("4631") {
@ -108,7 +116,7 @@ struct NodeDetail: View {
) {
Button("Shutdown Node?", role: .destructive) {
if !bleManager.sendShutdown(destNum: node.num, wantResponse: false) {
if !bleManager.sendShutdown(destNum: node.num, wantResponse: true) {
print("Shutdown Failed")
}
@ -129,215 +137,284 @@ struct NodeDetail: View {
.controlSize(.large)
.padding()
.confirmationDialog(
"Are you sure?",
isPresented: $isPresentingRebootConfirm
) {
Button("Reboot Node?", role: .destructive) {
if !bleManager.sendReboot(destNum: node.num, wantResponse: false) {
if !bleManager.sendReboot(destNum: node.num, wantResponse: true) {
print("Reboot Failed")
}
}
}
}
}
.padding(5)
Divider()
HStack {
Image(systemName: "clock.badge.checkmark.fill")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
LastHeardText(lastHeard: node.lastHeard).font(.title3)
}
.padding()
Divider()
HStack {
VStack(alignment: .center) {
Text("AKA").font(.title2).fixedSize()
CircleText(text: node.user?.shortName ?? "???", color: .accentColor)
.offset(y: 10)
}
.padding(5)
Divider()
VStack {
if node.user != nil {
Image(node.user!.hwModel ?? "UNSET")
.resizable()
.frame(width: 50, height: 50)
.cornerRadius(5)
Text(String(node.user!.hwModel ?? "UNSET"))
.font(.callout).fixedSize()
}
}
.padding(5)
}
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
// Add a divider if there is no map
if (node.positions?.count ?? 0) == 0 {
Divider()
}
if node.snr > 0 {
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("SNR").font(.title2).fixedSize()
Text(String(node.snr))
.font(.title2)
.foregroundColor(.gray)
.fixedSize()
}
.padding(5)
}
if node.telemetries?.count ?? 0 >= 1 {
let mostRecent = node.telemetries?.lastObject as! TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor)
.padding(.bottom)
if mostRecent.batteryLevel > 0 {
Text(String(mostRecent.batteryLevel) + "%")
.font(.title3)
.foregroundColor(.gray)
.fixedSize()
}
if mostRecent.voltage > 0 {
Text(String(format: "%.2f", mostRecent.voltage) + " V")
.font(.title3)
.foregroundColor(.gray)
.fixedSize()
}
}
.padding(5)
}
}
.padding(4)
Divider()
HStack(alignment: .center) {
VStack {
HStack {
Image(systemName: "person")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("User Id:").font(.title2)
}
Text(node.user?.userId ?? "??????").font(.title3).foregroundColor(.gray)
}
Divider()
VStack {
HStack {
Image(systemName: "number")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Node Number:").font(.title2)
}
Text(String(node.num)).font(.title3).foregroundColor(.gray)
}
}
.padding(5)
Divider()
HStack {
Image(systemName: "globe")
.font(.headline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("MAC Address: ")
Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray)
}
.padding()
if node.positions?.count ?? 0 >= 1 {
Divider()
HStack {
VStack(alignment: .center) {
Text("AKA").font(.largeTitle)
.foregroundColor(.gray).fixedSize()
.offset(y:20)
CircleText(text: node.user?.shortName ?? "???", color: .accentColor, circleSize: 75, fontSize: 26)
}
.padding()
Image(systemName: "location.circle.fill")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Location History").font(.title2)
Divider()
VStack {
if node.user != nil {
Image(hwModelString)
.resizable()
.frame(width: 90, height: 90)
.cornerRadius(5)
Text(String(hwModelString))
.foregroundColor(.gray)
.font(.largeTitle).fixedSize()
}
}
.padding()
if node.snr > 0 {
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
.padding(.bottom, 10)
Text("SNR").font(.largeTitle).fixedSize()
Text(String(node.snr))
.font(.largeTitle)
.foregroundColor(.gray)
.fixedSize()
}
}
if node.telemetries?.count ?? 0 >= 1 {
let mostRecent = node.telemetries?.lastObject as! TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .largeTitle, color: .accentColor)
.padding(.bottom, 10)
if mostRecent.batteryLevel > 0 {
Text(String(mostRecent.batteryLevel) + "%")
.font(.largeTitle)
.frame(width: 100)
.foregroundColor(.gray)
.fixedSize()
}
if mostRecent.voltage > 0 {
Text(String(format: "%.2f", mostRecent.voltage) + " V")
.font(.largeTitle)
.foregroundColor(.gray)
.fixedSize()
}
}
.padding()
}
}
.padding()
Divider()
ForEach(node.positions!.array as! [PositionEntity], id: \.self) { (mappin: PositionEntity) in
if mappin.coordinate != nil {
VStack {
HStack {
Image(systemName: "mappin.and.ellipse").foregroundColor(.accentColor) // .font(.subheadline)
Text("Lat/Long:").font(.caption)
Text("\(String(mappin.latitude ?? 0)) \(String(mappin.longitude ?? 0))")
.foregroundColor(.gray)
.font(.caption)
Image(systemName: "arrow.up.arrow.down.circle")
.font(.subheadline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Alt:")
.font(.caption)
Text("\(String(mappin.altitude))m")
.foregroundColor(.gray)
.font(.caption)
}
HStack {
Image(systemName: "clock.badge.checkmark.fill")
.font(.subheadline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Time:")
.font(.caption)
DateTimeText(dateTime: mappin.time)
.foregroundColor(.gray)
.font(.caption)
Divider()
}
HStack(alignment: .center) {
VStack {
HStack {
Image(systemName: "person")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("User Id:").font(.title)
}
Text(node.user?.userId ?? "??????").font(.title).foregroundColor(.gray)
}
Divider()
VStack {
HStack {
Image(systemName: "number")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Node Number:").font(.title)
}
Text(String(node.num)).font(.title).foregroundColor(.gray)
}
Divider()
VStack{
HStack {
Image(systemName: "globe")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("MAC Address: ").font(.title)
}
Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address"))
.font(.title)
.foregroundColor(.gray)
}
Divider()
VStack{
HStack {
Image(systemName: "clock.badge.checkmark.fill")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Last Heard: ").font(.title)
}
DateTimeText(dateTime: node.lastHeard)
.font(.title)
.foregroundColor(.gray)
}
}
.padding()
Divider()
} else {
HStack {
VStack(alignment: .center) {
Text("AKA").font(.title2).fixedSize()
CircleText(text: node.user?.shortName ?? "???", color: .accentColor)
.offset(y: 10)
}
.padding(5)
Divider()
VStack {
if node.user != nil {
Image(node.user!.hwModel ?? "UNSET")
.resizable()
.frame(width: 50, height: 50)
.cornerRadius(5)
Text(String(node.user!.hwModel ?? "UNSET"))
.font(.callout).fixedSize()
}
}
.padding(5)
if node.snr > 0 {
Divider()
VStack(alignment: .center) {
Image(systemName: "waveform.path")
.font(.title)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("SNR").font(.title2).fixedSize()
Text(String(node.snr))
.font(.title2)
.foregroundColor(.gray)
.fixedSize()
}
.padding(5)
}
if node.telemetries?.count ?? 0 >= 1 {
let mostRecent = node.telemetries?.lastObject as! TelemetryEntity
Divider()
VStack(alignment: .center) {
BatteryIcon(batteryLevel: mostRecent.batteryLevel, font: .title, color: .accentColor)
.padding(.bottom)
if mostRecent.batteryLevel > 0 {
Text(String(mostRecent.batteryLevel) + "%")
.font(.title3)
.foregroundColor(.gray)
.fixedSize()
}
if mostRecent.voltage > 0 {
Text(String(format: "%.2f", mostRecent.voltage) + " V")
.font(.title3)
.foregroundColor(.gray)
.fixedSize()
}
}
.padding(5)
}
}
.padding(4)
HStack(alignment: .center) {
VStack {
HStack {
Image(systemName: "person")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("User Id:").font(.title2)
}
Text(node.user?.userId ?? "??????").font(.title3).foregroundColor(.gray)
}
Divider()
VStack {
HStack {
Image(systemName: "number")
.font(.title2)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("Node Number:").font(.title2)
}
Text(String(node.num)).font(.title3).foregroundColor(.gray)
}
}
.padding(5)
Divider()
HStack {
Image(systemName: "globe")
.font(.headline)
.foregroundColor(.accentColor)
.symbolRenderingMode(.hierarchical)
Text("MAC Address: ")
Text(String(node.user?.macaddr?.macAddressString ?? "not a valid mac address")).foregroundColor(.gray)
}
.padding()
}
}
}
.edgesIgnoringSafeArea([.leading, .trailing])
.padding(1)
}
}
.navigationTitle((node.user != nil) ? String(node.user!.longName ?? "Unknown") : "Unknown")
.navigationBarTitleDisplayMode(.inline)
.navigationBarTitleDisplayMode(.automatic)
.navigationBarItems(trailing:
ZStack {
@ -348,12 +425,15 @@ struct NodeDetail: View {
name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "????")
}
)
.onAppear(perform: {
.onAppear {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
if self.initialLoad{
self.bleManager.context = context
self.initialLoad = false
}
}
})
}
}

View file

@ -15,14 +15,16 @@ struct NodeList: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
@State var initialLoad: Bool = true
@FetchRequest(
sortDescriptors: [NSSortDescriptor(key: "lastHeard", ascending: false)],
animation: .default)
private var nodes: FetchedResults<NodeInfoEntity>
private var nodes: FetchedResults<NodeInfoEntity>
@State private var selection: String?
@State private var selection: String? = ""
var body: some View {
@ -102,13 +104,17 @@ struct NodeList: View {
.navigationTitle("All Nodes")
.onAppear {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
if initialLoad {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
self.initialLoad = false
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
if nodes.count > 0 {
selection = "0"
if nodes.count > 0 {
selection = "0"
}
}
}
}

View file

@ -16,7 +16,6 @@ import CoreLocation
struct AdminMessageList: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var user: UserEntity?
@ -38,10 +37,7 @@ struct AdminMessageList: View {
Image(systemName: "checkmark.square")
.foregroundColor(.gray)
.font(.caption)
Text("Acknowledged: ")
.foregroundColor(.gray)
.font(.caption)
DateTimeText(dateTime: Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)))
Text("Acknowledged: \(Date(timeIntervalSince1970: TimeInterval(am.messageTimestamp)), style: .time)")
.foregroundColor(.gray)
.font(.caption)
@ -49,7 +45,7 @@ struct AdminMessageList: View {
Image(systemName: "square")
.foregroundColor(.gray)
.font(.caption)
Text("Acknowledged")
Text("Not Acknowledged")
.foregroundColor(.gray)
.font(.caption)
}
@ -57,7 +53,7 @@ struct AdminMessageList: View {
}
}
}
.navigationTitle("Admin Message History")
.navigationTitle("Admin Message Log")
.navigationBarItems(trailing:
ZStack {

View file

@ -119,28 +119,8 @@ struct AppSettings: View {
.foregroundColor(.gray)
}
Section(header: Text("OPTIONS")) {
Section(header: Text("Options")) {
Toggle(isOn: $userSettings.provideLocation) {
Label("Provide location to mesh", systemImage: "location.circle.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.provideLocation {
Picker(" Update Interval", selection: $userSettings.provideLocationInterval) {
ForEach(LocationUpdateInterval.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.")
.font(.caption)
.listRowSeparator(.visible)
}
Picker("Keyboard Type", selection: $userSettings.keyboardType) {
ForEach(KeyboardType.allCases) { kb in
Text(kb.description)
@ -156,6 +136,28 @@ struct AppSettings: View {
.pickerStyle(DefaultPickerStyle())
}
Section(header: Text("Phone GPS")) {
Toggle(isOn: $userSettings.provideLocation) {
Label("Provide location to mesh", systemImage: "location.circle.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.provideLocation {
Picker(" Update Interval", selection: $userSettings.provideLocationInterval) {
ForEach(LocationUpdateInterval.allCases) { lu in
Text(lu.description)
}
}
.pickerStyle(DefaultPickerStyle())
Text("How frequently your phone will send your location to the device, location updates to the mesh are managed by the device.")
.font(.caption)
.listRowSeparator(.visible)
}
}
}
}
.navigationTitle("App Settings")

View file

@ -51,7 +51,7 @@ struct DeviceConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingFactoryResetConfirm: Bool = false
@State private var isPresentingSaveConfirm: Bool = false
@ -124,7 +124,7 @@ struct DeviceConfig: View {
dc.serialDisabled = !serialEnabled
dc.debugLogEnabled = debugLogEnabled
let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveDeviceConfig(config: dc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0 {
@ -153,7 +153,7 @@ struct DeviceConfig: View {
) {
Button("Erase all device settings?", role: .destructive) {
if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: false) {
if !bleManager.sendFactoryReset(destNum: bleManager.connectedPeripheral.num, wantResponse: true) {
print("Factory Reset Failed")
}
@ -176,33 +176,24 @@ struct DeviceConfig: View {
self.bleManager.context = context
self.deviceRole = Int(node.deviceConfig?.role ?? 0)
self.serialEnabled = (node.deviceConfig?.serialEnabled ?? true)
self.debugLogEnabled = node.deviceConfig?.debugLogEnabled ?? false
self.deviceRole = Int(node!.deviceConfig?.role ?? 0)
self.serialEnabled = (node!.deviceConfig?.serialEnabled ?? true)
self.debugLogEnabled = node!.deviceConfig?.debugLogEnabled ?? false
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: deviceRole) { newRole in
if newRole != node.deviceConfig!.role {
hasChanges = true
}
if newRole != node!.deviceConfig!.role { hasChanges = true }
}
.onChange(of: serialEnabled) { newSerial in
if newSerial != node.deviceConfig!.serialEnabled {
hasChanges = true
}
if newSerial != node!.deviceConfig!.serialEnabled { hasChanges = true }
}
.onChange(of: debugLogEnabled) { newDebugLog in
if newDebugLog != node.deviceConfig!.debugLogEnabled {
hasChanges = true
}
if newDebugLog != node!.deviceConfig!.debugLogEnabled { hasChanges = true }
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -58,22 +58,18 @@ enum GpsFormats: Int, CaseIterable, Identifiable {
// Default of 0 is One Minute
enum ScreenOnIntervals: Int, CaseIterable, Identifiable {
case fifteenSeconds = 15
case thirtySeconds = 30
case oneMinute = 0
case oneMinute = 60
case fiveMinutes = 300
case tenMinutes = 600
case tenMinutes = 0
case fifteenMinutes = 900
case max = 2147483647
case thirtyMinutes = 1800
case oneHour = 3600
case max = 31536000 // One Year
var id: Int { self.rawValue }
var description: String {
get {
switch self {
case .fifteenSeconds:
return "Fifteen Seconds"
case .thirtySeconds:
return "Thirty Seconds"
case .oneMinute:
return "One Minute"
case .fiveMinutes:
@ -82,6 +78,10 @@ enum ScreenOnIntervals: Int, CaseIterable, Identifiable {
return "Ten Minutes"
case .fifteenMinutes:
return "Fifteen Minutes"
case .thirtyMinutes:
return "Thirty Minutes"
case .oneHour:
return "One Hour"
case .max:
return "Always On"
}
@ -125,7 +125,8 @@ struct DisplayConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@State var hasChanges = false
@ -203,7 +204,7 @@ struct DisplayConfig: View {
dc.screenOnSecs = UInt32(screenOnSeconds)
dc.autoScreenCarouselSecs = UInt32(screenCarouselInterval)
let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveDisplayConfig(config: dc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0 {
@ -230,30 +231,30 @@ struct DisplayConfig: View {
self.bleManager.context = context
self.gpsFormat = Int(node.displayConfig?.gpsFormat ?? 0)
self.screenOnSeconds = Int(node.displayConfig?.screenOnSeconds ?? 0)
self.screenCarouselInterval = Int(node.displayConfig?.screenCarouselInterval ?? 0)
self.gpsFormat = Int(node!.displayConfig?.gpsFormat ?? 0)
self.screenOnSeconds = Int(node!.displayConfig?.screenOnSeconds ?? 0)
self.screenCarouselInterval = Int(node!.displayConfig?.screenCarouselInterval ?? 0)
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: screenOnSeconds) { newScreenSecs in
if newScreenSecs != node.displayConfig!.screenOnSeconds {
if newScreenSecs != node!.displayConfig!.screenOnSeconds {
hasChanges = true
}
}
.onChange(of: screenCarouselInterval) { newCarouselSecs in
if newCarouselSecs != node.displayConfig!.screenCarouselInterval {
if newCarouselSecs != node!.displayConfig!.screenCarouselInterval {
hasChanges = true
}
}
.onChange(of: gpsFormat) { newGpsFormat in
if newGpsFormat != node.displayConfig!.gpsFormat {
if newGpsFormat != node!.displayConfig!.gpsFormat {
hasChanges = true
}

View file

@ -96,8 +96,8 @@ enum ModemPresets : Int, CaseIterable, Identifiable {
case LongFast = 0
case LongSlow = 1
case VLongSlow = 2
case MidSlow = 3
case MidFast = 4
case MedSlow = 3
case MedFast = 4
case ShortSlow = 5
case ShortFast = 6
@ -112,9 +112,9 @@ enum ModemPresets : Int, CaseIterable, Identifiable {
return "Long Range - Slow"
case .VLongSlow:
return "Very Long Range - Slow"
case .MidSlow:
case .MedSlow:
return "Medium Range - Slow"
case .MidFast:
case .MedFast:
return "Medium Range - Fast"
case .ShortSlow:
return "Short Range - Slow"
@ -133,9 +133,9 @@ enum ModemPresets : Int, CaseIterable, Identifiable {
return Config.LoRaConfig.ModemPreset.longSlow
case .VLongSlow:
return Config.LoRaConfig.ModemPreset.vlongSlow
case .MidSlow:
case .MedSlow:
return Config.LoRaConfig.ModemPreset.medSlow
case .MidFast:
case .MedFast:
return Config.LoRaConfig.ModemPreset.medFast
case .ShortSlow:
return Config.LoRaConfig.ModemPreset.shortSlow
@ -185,7 +185,7 @@ struct LoRaConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@ -263,7 +263,7 @@ struct LoRaConfig: View {
lc.region = RegionCodes(rawValue: region)!.protoEnumValue()
lc.modemPreset = ModemPresets(rawValue: modemPreset)!.protoEnumValue()
let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveLoRaConfig(config: lc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0 {
@ -290,35 +290,29 @@ struct LoRaConfig: View {
if self.initialLoad{
self.bleManager.context = context
self.hopLimit = Int(node.loRaConfig?.hopLimit ?? 0)
self.region = Int(node.loRaConfig?.regionCode ?? 0)
self.modemPreset = Int(node.loRaConfig?.modemPreset ?? 0)
self.hopLimit = Int(node!.loRaConfig?.hopLimit ?? 0)
self.region = Int(node!.loRaConfig?.regionCode ?? 0)
self.modemPreset = Int(node!.loRaConfig?.modemPreset ?? 0)
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: region) { newRegion in
if node.loRaConfig != nil {
if newRegion != node.loRaConfig!.regionCode {
hasChanges = true
}
if node!.loRaConfig != nil {
if newRegion != node!.loRaConfig!.regionCode { hasChanges = true }
}
}
.onChange(of: modemPreset) { newModemPreset in
if newModemPreset != node.loRaConfig!.modemPreset {
hasChanges = true
if node!.loRaConfig != nil {
if newModemPreset != node!.loRaConfig!.modemPreset { hasChanges = true }
}
}
.onChange(of: hopLimit) { newHopLimit in
if newHopLimit != node.loRaConfig!.hopLimit {
hasChanges = true
if node!.loRaConfig != nil {
if newHopLimit != node!.loRaConfig!.hopLimit { hasChanges = true }
}
}
.navigationViewStyle(StackNavigationViewStyle())

View file

@ -13,6 +13,7 @@ enum ConfigPresets : Int, CaseIterable, Identifiable {
case rakRotaryEncoder = 1
case tbeamThreeButtonScreen = 2
case cardKB = 3
case facesKB = 4
var id: Int { self.rawValue }
var description: String {
@ -26,7 +27,9 @@ enum ConfigPresets : Int, CaseIterable, Identifiable {
case .tbeamThreeButtonScreen:
return "TBEAM 3 Button OLED Screen"
case .cardKB:
return "Card KB"
return "M5 Stack Card KeyBoard"
case .facesKB:
return "M5 Stack Faces KeyBoard"
}
}
}
@ -97,7 +100,7 @@ struct CannedMessagesConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@ -260,7 +263,6 @@ struct CannedMessagesConfig: View {
}
.disabled(configPreset > 0)
}
.disabled(!(node.myInfo?.hasWifi ?? false))
Button {
@ -270,7 +272,7 @@ struct CannedMessagesConfig: View {
Label("Save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false))
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
@ -290,7 +292,7 @@ struct CannedMessagesConfig: View {
if rotary1Enabled {
/// Input event origin accepted by the canned messages
/// Can be e.g. "rotEnc1", "upDownEnc1" or keyword "_any"
/// Can be e.g. "rotEnc1", "upDownEnc1", "cardkb", "faceskb" 623or keyword "_any"
cmc.allowInputSource = "rotEnc1"
} else if updown1Enabled {
@ -308,7 +310,7 @@ struct CannedMessagesConfig: View {
cmc.inputbrokerEventCcw = InputEventChars(rawValue: inputbrokerEventCcw)!.protoEnumValue()
cmc.inputbrokerEventPress = InputEventChars(rawValue: inputbrokerEventPress)!.protoEnumValue()
let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveCannedMessageModuleConfig(config: cmc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0 {
// Should show a saved successfully alert once I know that to be true
@ -331,13 +333,13 @@ struct CannedMessagesConfig: View {
if self.initialLoad{
self.bleManager.context = context
self.enabled = node.cannedMessageConfig?.enabled ?? false
self.sendBell = node.cannedMessageConfig?.sendBell ?? false
self.rotary1Enabled = node.cannedMessageConfig?.rotary1Enabled ?? false
self.updown1Enabled = node.cannedMessageConfig?.updown1Enabled ?? false
self.inputbrokerPinA = Int(node.cannedMessageConfig?.inputbrokerPinA ?? 0)
self.inputbrokerPinB = Int(node.cannedMessageConfig?.inputbrokerPinB ?? 0)
self.inputbrokerPinPress = Int(node.cannedMessageConfig?.inputbrokerPinPress ?? 0)
self.enabled = node!.cannedMessageConfig?.enabled ?? false
self.sendBell = node!.cannedMessageConfig?.sendBell ?? false
self.rotary1Enabled = node!.cannedMessageConfig?.rotary1Enabled ?? false
self.updown1Enabled = node!.cannedMessageConfig?.updown1Enabled ?? false
self.inputbrokerPinA = Int(node!.cannedMessageConfig?.inputbrokerPinA ?? 0)
self.inputbrokerPinB = Int(node!.cannedMessageConfig?.inputbrokerPinB ?? 0)
self.inputbrokerPinPress = Int(node!.cannedMessageConfig?.inputbrokerPinPress ?? 0)
self.hasChanges = false
self.initialLoad = false
}
@ -361,35 +363,35 @@ struct CannedMessagesConfig: View {
}
.onChange(of: enabled) { newEnabled in
if newEnabled != node.cannedMessageConfig!.enabled { hasChanges = true }
if newEnabled != node!.cannedMessageConfig!.enabled { hasChanges = true }
}
.onChange(of: sendBell) { newBell in
if newBell != node.cannedMessageConfig!.sendBell { hasChanges = true }
if newBell != node!.cannedMessageConfig!.sendBell { hasChanges = true }
}
.onChange(of: inputbrokerPinA) { newPinA in
if newPinA != node.cannedMessageConfig!.inputbrokerPinA { hasChanges = true }
if newPinA != node!.cannedMessageConfig!.inputbrokerPinA { hasChanges = true }
}
.onChange(of: inputbrokerPinB) { newPinB in
if newPinB != node.cannedMessageConfig!.inputbrokerPinB { hasChanges = true }
if newPinB != node!.cannedMessageConfig!.inputbrokerPinB { hasChanges = true }
}
.onChange(of: inputbrokerPinPress) { newPinPress in
if newPinPress != node.cannedMessageConfig!.inputbrokerPinPress { hasChanges = true }
if newPinPress != node!.cannedMessageConfig!.inputbrokerPinPress { hasChanges = true }
}
.onChange(of: inputbrokerEventCw) { newKeyA in
if newKeyA != node.cannedMessageConfig!.inputbrokerEventCw { hasChanges = true }
if newKeyA != node!.cannedMessageConfig!.inputbrokerEventCw { hasChanges = true }
}
.onChange(of: inputbrokerEventCcw) { newKeyB in
if newKeyB != node.cannedMessageConfig!.inputbrokerEventCcw { hasChanges = true }
if newKeyB != node!.cannedMessageConfig!.inputbrokerEventCcw { hasChanges = true }
}
.onChange(of: inputbrokerEventPress) { newKeyPress in
if newKeyPress != node.cannedMessageConfig!.inputbrokerEventPress { hasChanges = true }
if newKeyPress != node!.cannedMessageConfig!.inputbrokerEventPress { hasChanges = true }
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -51,7 +51,7 @@ struct ExternalNotificationConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@ -138,7 +138,7 @@ struct ExternalNotificationConfig: View {
Label("Save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false))
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node!.myInfo?.hasWifi ?? false))
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
@ -158,7 +158,7 @@ struct ExternalNotificationConfig: View {
enc.output = UInt32(output)
enc.outputMs = UInt32(outputMilliseconds)
let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveExternalNotificationModuleConfig(config: enc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0{
@ -185,12 +185,12 @@ struct ExternalNotificationConfig: View {
self.bleManager.context = context
self.enabled = node.externalNotificationConfig?.enabled ?? false
self.alertBell = node.externalNotificationConfig?.alertBell ?? false
self.alertMessage = node.externalNotificationConfig?.alertMessage ?? false
self.active = node.externalNotificationConfig?.active ?? false
self.output = Int(node.externalNotificationConfig?.output ?? 0)
self.outputMilliseconds = Int(node.externalNotificationConfig?.outputMilliseconds ?? 0)
self.enabled = node!.externalNotificationConfig?.enabled ?? false
self.alertBell = node!.externalNotificationConfig?.alertBell ?? false
self.alertMessage = node!.externalNotificationConfig?.alertMessage ?? false
self.active = node!.externalNotificationConfig?.active ?? false
self.output = Int(node!.externalNotificationConfig?.output ?? 0)
self.outputMilliseconds = Int(node!.externalNotificationConfig?.outputMilliseconds ?? 0)
self.hasChanges = false
self.initialLoad = false
@ -198,27 +198,27 @@ struct ExternalNotificationConfig: View {
}
.onChange(of: enabled) { newEnabled in
if newEnabled != node.externalNotificationConfig!.enabled { hasChanges = true }
if newEnabled != node!.externalNotificationConfig!.enabled { hasChanges = true }
}
.onChange(of: alertBell) { newAlertBell in
if newAlertBell != node.externalNotificationConfig!.alertBell { hasChanges = true }
if newAlertBell != node!.externalNotificationConfig!.alertBell { hasChanges = true }
}
.onChange(of: alertMessage) { newAlertMessage in
if newAlertMessage != node.externalNotificationConfig!.alertMessage { hasChanges = true }
if newAlertMessage != node!.externalNotificationConfig!.alertMessage { hasChanges = true }
}
.onChange(of: active) { newActuve in
if newActuve != node.externalNotificationConfig!.active { hasChanges = true }
if newActuve != node!.externalNotificationConfig!.active { hasChanges = true }
}
.onChange(of: output) { newOutput in
if newOutput != node.externalNotificationConfig!.output { hasChanges = true }
if newOutput != node!.externalNotificationConfig!.output { hasChanges = true }
}
.onChange(of: outputMilliseconds) { newOutputMs in
if newOutputMs != node.externalNotificationConfig!.outputMilliseconds { hasChanges = true }
if newOutputMs != node!.externalNotificationConfig!.outputMilliseconds { hasChanges = true }
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -42,7 +42,7 @@ struct RangeTestConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@ -80,13 +80,13 @@ struct RangeTestConfig: View {
Label("Save", systemImage: "square.and.arrow.down.fill")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
.disabled(!(node.myInfo?.hasWifi ?? false))
.disabled(!(node!.myInfo?.hasWifi ?? false))
Text("Saves a CSV with the range test message details, currently only available on ESP32 devices with a web server.")
.font(.caption)
}
}
.disabled(!(node.myInfo?.hasWifi ?? false))
.disabled(!(node!.myInfo?.hasWifi ?? false))
Button {
@ -96,7 +96,7 @@ struct RangeTestConfig: View {
Label("Save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false))
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node!.myInfo?.hasWifi ?? false))
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
@ -113,7 +113,7 @@ struct RangeTestConfig: View {
rtc.save = save
rtc.sender = UInt32(sender)
let adminMessageId = bleManager.saveRangeTestModuleConfig(config: rtc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveRangeTestModuleConfig(config: rtc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0 {
@ -139,33 +139,24 @@ struct RangeTestConfig: View {
if self.initialLoad{
self.bleManager.context = context
self.enabled = node.rangeTestConfig?.enabled ?? false
self.save = node.rangeTestConfig?.save ?? false
self.sender = Int(node.rangeTestConfig?.sender ?? 0)
self.enabled = node!.rangeTestConfig?.enabled ?? false
self.save = node!.rangeTestConfig?.save ?? false
self.sender = Int(node!.rangeTestConfig?.sender ?? 0)
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: enabled) { newEnabled in
if newEnabled != node.rangeTestConfig!.enabled {
hasChanges = true
}
if newEnabled != node!.rangeTestConfig!.enabled { hasChanges = true }
}
.onChange(of: save) { newSave in
if newSave != node.rangeTestConfig!.save {
hasChanges = true
}
if newSave != node!.rangeTestConfig!.save { hasChanges = true }
}
.onChange(of: sender) { newSender in
if newSender != node.rangeTestConfig!.sender {
hasChanges = true
}
if newSender != node!.rangeTestConfig!.sender { hasChanges = true }
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -178,7 +178,7 @@ struct SerialConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@ -302,7 +302,7 @@ struct SerialConfig: View {
sc.timeout = UInt32(timeout)
sc.mode = SerialModeTypes(rawValue: mode)!.protoEnumValue()
let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveSerialModuleConfig(config: sc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0 {
@ -329,13 +329,13 @@ struct SerialConfig: View {
self.bleManager.context = context
self.enabled = node.serialConfig?.enabled ?? false
self.echo = node.serialConfig?.echo ?? false
self.rxd = Int(node.serialConfig?.rxd ?? 0)
self.txd = Int(node.serialConfig?.txd ?? 0)
self.baudRate = Int(node.serialConfig?.baudRate ?? 0)
self.timeout = Int(node.serialConfig?.timeout ?? 0)
self.mode = Int(node.serialConfig?.mode ?? 0)
self.enabled = node!.serialConfig?.enabled ?? false
self.echo = node!.serialConfig?.echo ?? false
self.rxd = Int(node!.serialConfig?.rxd ?? 0)
self.txd = Int(node!.serialConfig?.txd ?? 0)
self.baudRate = Int(node!.serialConfig?.baudRate ?? 0)
self.timeout = Int(node!.serialConfig?.timeout ?? 0)
self.mode = Int(node!.serialConfig?.mode ?? 0)
self.hasChanges = false
self.initialLoad = false
@ -343,31 +343,31 @@ struct SerialConfig: View {
}
.onChange(of: enabled) { newEnabled in
if newEnabled != node.serialConfig!.enabled { hasChanges = true }
if newEnabled != node!.serialConfig!.enabled { hasChanges = true }
}
.onChange(of: echo) { newEcho in
if newEcho != node.serialConfig!.echo { hasChanges = true }
if newEcho != node!.serialConfig!.echo { hasChanges = true }
}
.onChange(of: rxd) { newRxd in
if newRxd != node.serialConfig!.rxd { hasChanges = true }
if newRxd != node!.serialConfig!.rxd { hasChanges = true }
}
.onChange(of: txd) { newTxd in
if newTxd != node.serialConfig!.txd { hasChanges = true }
if newTxd != node!.serialConfig!.txd { hasChanges = true }
}
.onChange(of: baudRate) { newBaud in
if newBaud != node.serialConfig!.baudRate { hasChanges = true }
if newBaud != node!.serialConfig!.baudRate { hasChanges = true }
}
.onChange(of: timeout) { newTimeout in
if newTimeout != node.serialConfig!.timeout { hasChanges = true }
if newTimeout != node!.serialConfig!.timeout { hasChanges = true }
}
.onChange(of: mode) { newMode in
if newMode != node.serialConfig!.mode { hasChanges = true }
if newMode != node!.serialConfig!.mode { hasChanges = true }
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -179,7 +179,7 @@ struct TelemetryConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@ -289,7 +289,7 @@ struct TelemetryConfig: View {
Label("Save", systemImage: "square.and.arrow.down")
}
.disabled(bleManager.connectedPeripheral == nil || !hasChanges || !(node.myInfo?.hasWifi ?? false))
.disabled(bleManager.connectedPeripheral == nil || !hasChanges)
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)
.controlSize(.large)
@ -311,7 +311,7 @@ struct TelemetryConfig: View {
tc.environmentRecoveryInterval = UInt32(environmentRecoveryInterval)
tc.environmentReadErrorCountThreshold = UInt32(environmentReadErrorCountThreshold)
let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveTelemetryModuleConfig(config: tc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0 {
@ -338,14 +338,14 @@ struct TelemetryConfig: View {
self.bleManager.context = context
self.deviceUpdateInterval = Int(node.telemetryConfig?.deviceUpdateInterval ?? 0)
self.environmentUpdateInterval = Int(node.telemetryConfig?.environmentUpdateInterval ?? 0)
self.environmentMeasurementEnabled = node.telemetryConfig?.environmentMeasurementEnabled ?? false
self.environmentSensorType = Int(node.telemetryConfig?.environmentSensorType ?? 0)
self.environmentScreenEnabled = node.telemetryConfig?.environmentScreenEnabled ?? false
self.environmentDisplayFahrenheit = node.telemetryConfig?.environmentDisplayFahrenheit ?? false
self.environmentRecoveryInterval = Int(node.telemetryConfig?.environmentRecoveryInterval ?? 0)
self.environmentReadErrorCountThreshold = Int(node.telemetryConfig?.environmentReadErrorCountThreshold ?? 0)
self.deviceUpdateInterval = Int(node!.telemetryConfig?.deviceUpdateInterval ?? 0)
self.environmentUpdateInterval = Int(node!.telemetryConfig?.environmentUpdateInterval ?? 0)
self.environmentMeasurementEnabled = node!.telemetryConfig?.environmentMeasurementEnabled ?? false
self.environmentSensorType = Int(node!.telemetryConfig?.environmentSensorType ?? 0)
self.environmentScreenEnabled = node!.telemetryConfig?.environmentScreenEnabled ?? false
self.environmentDisplayFahrenheit = node!.telemetryConfig?.environmentDisplayFahrenheit ?? false
self.environmentRecoveryInterval = Int(node!.telemetryConfig?.environmentRecoveryInterval ?? 0)
self.environmentReadErrorCountThreshold = Int(node!.telemetryConfig?.environmentReadErrorCountThreshold ?? 0)
self.hasChanges = false
self.initialLoad = false
@ -353,35 +353,35 @@ struct TelemetryConfig: View {
}
.onChange(of: deviceUpdateInterval) { newDeviceInterval in
if newDeviceInterval != node.telemetryConfig!.deviceUpdateInterval { hasChanges = true }
if newDeviceInterval != node!.telemetryConfig!.deviceUpdateInterval { hasChanges = true }
}
.onChange(of: environmentUpdateInterval) { newEnvInterval in
if newEnvInterval != node.telemetryConfig!.environmentUpdateInterval { hasChanges = true }
if newEnvInterval != node!.telemetryConfig!.environmentUpdateInterval { hasChanges = true }
}
.onChange(of: environmentMeasurementEnabled) { newEnvEnabled in
if newEnvEnabled != node.telemetryConfig!.environmentMeasurementEnabled { hasChanges = true }
if newEnvEnabled != node!.telemetryConfig!.environmentMeasurementEnabled { hasChanges = true }
}
.onChange(of: environmentSensorType) { newEnvSensorType in
if newEnvSensorType != node.telemetryConfig!.environmentSensorType { hasChanges = true }
if newEnvSensorType != node!.telemetryConfig!.environmentSensorType { hasChanges = true }
}
.onChange(of: environmentScreenEnabled) { newEnvScreenEnabled in
if newEnvScreenEnabled != node.telemetryConfig!.environmentScreenEnabled { hasChanges = true }
if newEnvScreenEnabled != node!.telemetryConfig!.environmentScreenEnabled { hasChanges = true }
}
.onChange(of: environmentDisplayFahrenheit) { newEnvDisplayF in
if newEnvDisplayF != node.telemetryConfig!.environmentDisplayFahrenheit { hasChanges = true }
if newEnvDisplayF != node!.telemetryConfig!.environmentDisplayFahrenheit { hasChanges = true }
}
.onChange(of: environmentRecoveryInterval) { newEnvRecoveryInterval in
if newEnvRecoveryInterval != node.telemetryConfig!.environmentRecoveryInterval { hasChanges = true }
if newEnvRecoveryInterval != node!.telemetryConfig!.environmentRecoveryInterval { hasChanges = true }
}
.onChange(of: environmentReadErrorCountThreshold) { newEnvReadErrorCountThreshold in
if newEnvReadErrorCountThreshold != node.telemetryConfig!.environmentReadErrorCountThreshold { hasChanges = true }
if newEnvReadErrorCountThreshold != node!.telemetryConfig!.environmentReadErrorCountThreshold { hasChanges = true }
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -111,7 +111,8 @@ struct PositionConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingSaveConfirm: Bool = false
@State var initialLoad: Bool = true
@State var hasChanges = false
@ -282,7 +283,7 @@ struct PositionConfig: View {
pc.gpsAttemptTime = UInt32(gpsAttemptTime)
pc.positionBroadcastSecs = UInt32(positionBroadcastSeconds)
let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.savePositionConfig(config: pc, fromUser: node!.user!, toUser: node!.user!, wantResponse: true)
if adminMessageId > 0{
@ -308,36 +309,27 @@ struct PositionConfig: View {
if self.initialLoad{
self.bleManager.context = context
self.smartPositionEnabled = node.positionConfig?.smartPositionEnabled ?? true
self.deviceGpsEnabled = node.positionConfig?.deviceGpsEnabled ?? true
self.fixedPosition = node.positionConfig?.fixedPosition ?? false
self.gpsUpdateInterval = Int(node.positionConfig?.gpsUpdateInterval ?? 0)
self.gpsAttemptTime = Int(node.positionConfig?.gpsAttemptTime ?? 0)
self.positionBroadcastSeconds = Int(node.positionConfig?.positionBroadcastSeconds ?? 0)
self.smartPositionEnabled = node!.positionConfig?.smartPositionEnabled ?? true
self.deviceGpsEnabled = node!.positionConfig?.deviceGpsEnabled ?? true
self.fixedPosition = node!.positionConfig?.fixedPosition ?? false
self.gpsUpdateInterval = Int(node!.positionConfig?.gpsUpdateInterval ?? 0)
self.gpsAttemptTime = Int(node!.positionConfig?.gpsAttemptTime ?? 0)
self.positionBroadcastSeconds = Int(node!.positionConfig?.positionBroadcastSeconds ?? 0)
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: smartPositionEnabled) { newSmartPosition in
if newSmartPosition != node.positionConfig!.smartPositionEnabled {
hasChanges = true
}
if newSmartPosition != node!.positionConfig!.smartPositionEnabled { hasChanges = true }
}
.onChange(of: deviceGpsEnabled) { newDeviceGps in
if newDeviceGps != node.positionConfig!.deviceGpsEnabled {
hasChanges = true
}
if newDeviceGps != node!.positionConfig!.deviceGpsEnabled { hasChanges = true }
}
.onChange(of: fixedPosition) { newFixed in
if newFixed != node.positionConfig!.fixedPosition {
hasChanges = true
}
if newFixed != node!.positionConfig!.fixedPosition { hasChanges = true }
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -74,7 +74,7 @@ struct MeshLog: View {
} label: {
Label("Download Log", systemImage: "arrow.down.circle.fill")
Label("Save Log", systemImage: "square.and.arrow.down")
}
.buttonStyle(.bordered)
.buttonBorderShape(.capsule)

View file

@ -19,18 +19,17 @@ struct Settings: View {
private var nodes: FetchedResults<NodeInfoEntity>
@State private var selection: String? = ""
var body: some View {
NavigationView {
List {
let connectedNodeNum = bleManager.connectedPeripheral != nil ? bleManager.connectedPeripheral.num : 0
NavigationLink(tag: String("0"), selection: $selection) {
NavigationLink() {
AppSettings()
} label: {
Image(systemName: "gearshape")
.symbolRenderingMode(.hierarchical)
Text("App Settings")
@ -39,7 +38,7 @@ struct Settings: View {
Section("Radio Configuration") {
NavigationLink {
ShareChannel(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
ShareChannel(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "qrcode")
.symbolRenderingMode(.hierarchical)
@ -48,7 +47,7 @@ struct Settings: View {
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
UserConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
UserConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "person.crop.rectangle.fill")
@ -60,7 +59,7 @@ struct Settings: View {
NavigationLink() {
LoRaConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
LoRaConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "dot.radiowaves.left.and.right")
@ -71,7 +70,7 @@ struct Settings: View {
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
DeviceConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "flipphone")
@ -81,7 +80,7 @@ struct Settings: View {
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
DisplayConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
DisplayConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "display")
@ -91,7 +90,7 @@ struct Settings: View {
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
PositionConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "location")
@ -108,7 +107,7 @@ struct Settings: View {
Section("Module Configuration") {
NavigationLink {
CannedMessagesConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
CannedMessagesConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "list.bullet.rectangle.fill")
@ -119,7 +118,7 @@ struct Settings: View {
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
ExternalNotificationConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
ExternalNotificationConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "megaphone")
@ -130,7 +129,7 @@ struct Settings: View {
.disabled(bleManager.connectedPeripheral == nil)
NavigationLink {
RangeTestConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
RangeTestConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "point.3.connected.trianglepath.dotted")
@ -139,11 +138,9 @@ struct Settings: View {
Text("Range Test")
}
.disabled(bleManager.connectedPeripheral == nil)
//nodes.first(where: { $0.num == connectedNodeNum })?.myInfo?.hasWifi ?? true)//||
// nodes.first(where: { $0.num == connectedNodeNum })!.rangeTestConfig != nil)
NavigationLink {
SerialConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
SerialConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "terminal")
@ -155,7 +152,7 @@ struct Settings: View {
NavigationLink {
TelemetryConfig(node: nodes.first(where: { $0.num == connectedNodeNum }) ?? NodeInfoEntity())
TelemetryConfig(node: nodes.first(where: { $0.num == connectedNodeNum }))
} label: {
Image(systemName: "chart.xyaxis.line")
@ -183,7 +180,7 @@ struct Settings: View {
let connectedNode = nodes.first(where: { $0.num == connectedNodeNum })
AdminMessageList(user: connectedNode?.user ?? UserEntity())
AdminMessageList(user: connectedNode?.user)
} label: {
Image(systemName: "building.columns")
@ -203,13 +200,7 @@ struct Settings: View {
self.bleManager.context = context
self.bleManager.userSettings = userSettings
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
if nodes.count > 0 {
selection = "0"
}
}
}
.listStyle(GroupedListStyle())
.navigationTitle("Settings")

View file

@ -37,7 +37,7 @@ struct ShareChannel: View {
@EnvironmentObject var bleManager: BLEManager
@EnvironmentObject var userSettings: UserSettings
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var text = "https://meshtastic.org/e/#test"
var qrCodeImage = QrCodeImage()

View file

@ -11,7 +11,7 @@ struct UserConfig: View {
@Environment(\.managedObjectContext) var context
@EnvironmentObject var bleManager: BLEManager
var node: NodeInfoEntity
var node: NodeInfoEntity?
@State private var isPresentingFactoryResetConfirm: Bool = false
@State private var isPresentingSaveConfirm: Bool = false
@ -112,7 +112,7 @@ struct UserConfig: View {
u.shortName = shortName
u.longName = longName
let adminMessageId = bleManager.saveUser(config: u, fromUser: node.user!, toUser: node.user!, wantResponse: true)
let adminMessageId = bleManager.saveUser(config: u, fromUser: node?.user, toUser: node?.user, wantResponse: true)
if adminMessageId > 0 {
@ -137,22 +137,22 @@ struct UserConfig: View {
self.bleManager.context = context
self.shortName = node.user!.shortName ?? ""
self.longName = node.user!.longName ?? ""
self.shortName = node?.user!.shortName ?? ""
self.longName = node?.user!.longName ?? ""
self.hasChanges = false
self.initialLoad = false
}
}
.onChange(of: shortName) { newShort in
if newShort != node.user!.shortName {
if newShort != node?.user!.shortName {
hasChanges = true
}
}
.onChange(of: longName) { newLong in
if newLong != node.user!.longName {
if newLong != node?.user!.longName {
hasChanges = true
}