V 1.26.4 Mesh Logging and automatically select messages

This commit is contained in:
Garth Vander Houwen 2021-10-20 00:31:22 -07:00
parent c10e70491e
commit d447aa1ca7
10 changed files with 335 additions and 145 deletions

View file

@ -20,6 +20,8 @@
DD47E3DF26F39D9F00029299 /* MyInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */; };
DD4A911E2708C65400501B7E /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911D2708C65400501B7E /* AppSettings.swift */; };
DD4A91202708C66600501B7E /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A911F2708C66600501B7E /* Configuration.swift */; };
DD8169F9271F1A6100F4AB02 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169F8271F1A6100F4AB02 /* Logger.swift */; };
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */; };
DD836AE726F6B38600ABCC23 /* Connect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AE626F6B38600ABCC23 /* Connect.swift */; };
DD836AED26F858F900ABCC23 /* MeshData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AEC26F858F900ABCC23 /* MeshData.swift */; };
DD836AEF26F85D8D00ABCC23 /* NodeInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD836AEE26F85D8D00ABCC23 /* NodeInfoModel.swift */; };
@ -80,6 +82,8 @@
DD47E3DE26F39D9F00029299 /* MyInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoModel.swift; sourceTree = "<group>"; };
DD4A911D2708C65400501B7E /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
DD4A911F2708C66600501B7E /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
DD8169F8271F1A6100F4AB02 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshLog.swift; sourceTree = "<group>"; };
DD836AE626F6B38600ABCC23 /* Connect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connect.swift; sourceTree = "<group>"; };
DD836AEC26F858F900ABCC23 /* MeshData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshData.swift; sourceTree = "<group>"; };
DD836AEE26F85D8D00ABCC23 /* NodeInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfoModel.swift; sourceTree = "<group>"; };
@ -165,6 +169,7 @@
children = (
DD4A911D2708C65400501B7E /* AppSettings.swift */,
DD4A911F2708C66600501B7E /* Configuration.swift */,
DD8169FA271F1F3A00F4AB02 /* MeshLog.swift */,
);
path = Settings;
sourceTree = "<group>";
@ -318,6 +323,7 @@
DDAF8C6D26ED19040058C060 /* Extensions.swift */,
DD47E3D126F1210600029299 /* HelperFunctions.swift */,
DD913638270DFF4C00D7ACF3 /* LocalNotificationManager.swift */,
DD8169F8271F1A6100F4AB02 /* Logger.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -487,7 +493,9 @@
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */,
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */,
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */,
DD47E3D926F3093800029299 /* MessageBubble.swift in Sources */,
DD8169F9271F1A6100F4AB02 /* Logger.swift in Sources */,
DDAF8C6726ED0C8C0058C060 /* remote_hardware.pb.swift in Sources */,
DDAF8C6526ED0A490058C060 /* channel.pb.swift in Sources */,
DD47E3DD26F390A000029299 /* Messages.swift in Sources */,
@ -660,7 +668,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.26.2;
MARKETING_VERSION = 1.26.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
@ -687,7 +695,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.26.2;
MARKETING_VERSION = 1.26.4;
PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;

View file

@ -7,6 +7,17 @@ import SwiftUI
// Meshtastic BLE Device Manager
//---------------------------------------------------------------------------------------
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
private static var documentsFolder: URL {
do {
return try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
} catch {
fatalError("Can't find documents directory.")
}
}
@ObservedObject private var meshData : MeshData
@ObservedObject private var messageData : MessageData
@ -20,6 +31,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
@Published var isSwitchedOn = false
@Published var peripherals = [Peripheral]()
private var meshLoggingEnabled: Bool = true
private var broadcastNodeId: UInt32 = 4294967295
@ -32,6 +45,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
let TORADIO_UUID = CBUUID(string: "0xF75C76D2-129E-4DAD-A1DD-7866124401E7")
let FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5")
let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453")
let meshLog = documentsFolder.appendingPathComponent("meshlog.txt")
/* init BLEManager */
override init() {
@ -88,7 +103,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
}
self.centralManager?.connect(peripheral)
print("Connected to: \(peripheral.name ?? "Unknown")")
if meshLoggingEnabled { Logger.log("BLE Connecting: \(peripheral.name ?? "Unknown")") }
print("BLE Connecting: \(peripheral.name ?? "Unknown")")
}
// Disconnect Device function
@ -122,63 +138,14 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
let deviceName = peripheral.name ?? ""
let peripheralLast4: String = String(deviceName.suffix(4))
print(peripheralLast4)
connectedNode = meshData.nodes.first(where: { $0.user.id.contains(peripheralLast4) })
lastConnectedPeripheral = peripheral.identifier.uuidString
peripheral.discoverServices([meshtasticServiceCBUUID])
print("Peripheral connected: " + peripheral.name!)
}
// Send Broadcast Message
public func sendMessage(message: String) -> Bool
{
var success = false
// Return false if we are not properly connected to a device, handle retry logic in the view for now
if connectedPeripheral == nil || connectedPeripheral!.peripheral.state != CBPeripheralState.connected || self.connectedNode == nil {
// Try and connect to the last connected device
self.disconnectDevice()
let lastConnectedPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == self.lastConnectedPeripheral }).first
if lastConnectedPeripheral != nil && lastConnectedPeripheral?.peripheral != nil {
connectTo(peripheral: lastConnectedPeripheral!.peripheral)
}
success = false
success = false
}
else if message.count < 1 {
// Don's send an empty message
success = false
}
else {
let messageModel = MessageModel(messageId: 0, messageTimeStamp: UInt32(Date().timeIntervalSince1970), fromUserId: self.connectedNode.num, toUserId: broadcastNodeId, fromUserLongName: self.connectedNode.user.longName, toUserLongName: "Broadcast", fromUserShortName: self.connectedNode.user.shortName, toUserShortName: "BC", receivedACK: false, messagePayload: message, direction: "OUT")
let dataType = PortNum.textMessageApp
let payloadData: Data = message.data(using: String.Encoding.utf8)!
var dataMessage = DataMessage()
dataMessage.payload = payloadData
dataMessage.portnum = dataType
var meshPacket = MeshPacket()
meshPacket.to = broadcastNodeId
meshPacket.decoded = dataMessage
meshPacket.wantAck = true
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket
let binaryData: Data = try! toRadio.serializedData()
if (connectedPeripheral!.peripheral.state == CBPeripheralState.connected)
{
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
messageData.messages.append(messageModel)
messageData.save()
success = true
}
}
return success
if meshLoggingEnabled { Logger.log("BLE Connected: \(peripheral.name ?? "Unknown")") }
print("BLE Connected: \(peripheral.name ?? "Unknown")")
peripherals.removeAll()
}
// Disconnect Peripheral Event
@ -188,15 +155,17 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
self.startScanning()
if let e = error {
print("Central disconnected because \(e)")
let errorCode = (e as NSError).code
if errorCode == 6 { // The connection has timed out unexpectedly.
// Happens when device is manually reset / powered off
// 2 second delay for device to power back on
let _ = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) in
let _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { (timer) in
//self.connectedPeripheral = nil
self.connectedNode = nil
self.connectTo(peripheral: peripheral)
}
}
@ -204,25 +173,28 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
// Seems to be what is received when a tbeam sleeps, immediately recconnecting does not work.
// Check if the last connected peripheral is still visible and then reconnect
connectedPeripheral = nil
connectedNode = nil
connectedPeripheral = nil
connectedNode = nil
}
else if errorCode == 14 { // Peer error that may require forgetting device in settings
// Forgetting and reconnecting seems to be necessary so we need to show the user an error telling them to do that
connectedPeripheral = nil
connectedNode = nil
lastConnectionError = (e as NSError).description
connectedPeripheral = nil
connectedNode = nil
}
print("Central disconnected because \(e)")
if meshLoggingEnabled { Logger.log("BLE Disconnected: \(peripheral.name ?? "Unknown") Error Code: \(errorCode) Error: \(e.localizedDescription)") }
} else {
// Disconnected without error which indicates user intent to disconnect
connectedPeripheral = nil
connectedNode = nil
if meshLoggingEnabled { Logger.log("BLE Disconnected: \(peripheral.name ?? "Unknown"): User Initiated Disconnect" ) }
print("Central disconnected! (no error)")
connectedPeripheral = nil
connectedNode = nil
}
print("Peripheral disconnected: " + peripheral.name!)
}
@ -238,11 +210,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
for service in services
{
print("Service discovered: " + service.uuid.uuidString)
if (service.uuid == meshtasticServiceCBUUID)
{
print ("Meshtastic service OK")
print("Meshtastic service discovered OK")
if meshLoggingEnabled { Logger.log("BLE Service for Meshtastic discovered by \(peripheral.name ?? "Unknown")") }
//peripheral.discoverCharacteristics(nil, for: service)
peripheral.discoverCharacteristics([TORADIO_UUID, FROMRADIO_UUID, FROMNUM_UUID], for: service)
}
@ -255,6 +228,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if let e = error {
print("Discover Characteristics error \(e)")
if meshLoggingEnabled { Logger.log("BLE didDiscoverCharacteristicsFor error by \(peripheral.name ?? "Unknown") \(e)") }
}
guard let characteristics = service.characteristics else { return }
@ -265,6 +239,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
{
case TORADIO_UUID:
print("TORADIO characteristic OK")
if meshLoggingEnabled { Logger.log("BLE did discover TORADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") }
TORADIO_characteristic = characteristic
var toRadio: ToRadio = ToRadio()
toRadio.wantConfigID = 32168
@ -274,12 +249,14 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
case FROMRADIO_UUID:
print("FROMRADIO characteristic OK")
if meshLoggingEnabled { Logger.log("BLE did discover FROMRADIO characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") }
FROMRADIO_characteristic = characteristic
peripheral.readValue(for: FROMRADIO_characteristic)
break
case FROMNUM_UUID:
print("FROMNUM (Notify) characteristic OK")
if meshLoggingEnabled { Logger.log("BLE did discover FROMNUM (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown")") }
FROMNUM_characteristic = characteristic
peripheral.setNotifyValue(true, for: characteristic)
break
@ -297,6 +274,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if let e = error {
print("didUpdateValueFor Characteristic error \(e)")
if meshLoggingEnabled { Logger.log("BLE didUpdateValueFor characteristic error by \(peripheral.name ?? "Unknown") \(e)") }
}
switch characteristic.uuid
@ -351,8 +329,8 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
meshData.nodes.append(connectedNode)
meshData.save()
print("Saved a myInfo for \(decodedInfo.myInfo.myNodeNum)")
if meshLoggingEnabled { Logger.log("BLE FROMRADIO received and myInfo saved for \(peripheral.name ?? "Unknown")") }
}
}
if decodedInfo.nodeInfo.num != 0 {
@ -412,6 +390,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
snr: decodedInfo.nodeInfo.snr)
)
meshData.save()
if meshLoggingEnabled { Logger.log("BLE FROMRADIO received and nodeInfo saved for \(decodedInfo.nodeInfo.user.longName)") }
}
}
// Handle assorted app packets
@ -425,6 +404,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
if let messageText = String(bytes: decodedInfo.packet.decoded.payload, encoding: .utf8) {
print("Message Text: \(messageText)")
if meshLoggingEnabled { Logger.log("BLE FROMRADIO received for text message app \(messageText)") }
let fromUser = meshData.nodes.first(where: { $0.id == decodedInfo.packet.from })
@ -466,11 +446,15 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
content: messageText)
]
manager.schedule()
if meshLoggingEnabled { Logger.log("iOS Notification Scheduled for text message from \(fromUser?.user.longName ?? "Unknown") \(messageText)") }
}
}
else if decodedInfo.packet.decoded.portnum == PortNum.nodeinfoApp {
var updatedNode = meshData.nodes.first(where: {$0.id == decodedInfo.packet.from })
if updatedNode != nil {
if meshLoggingEnabled { Logger.log("BLE FROMRADIO received for node info app for \(updatedNode!.user.longName)") }
}
if updatedNode != nil {
updatedNode!.snr = decodedInfo.packet.rxSnr
@ -480,6 +464,7 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
meshData.nodes.remove(at: nodeIndex!)
meshData.nodes.append(updatedNode!)
meshData.save()
if meshLoggingEnabled { Logger.log("Updated NodeInfo SNR and Time from Node Info App Packet For: \(updatedNode!.user.longName)") }
print("Updated NodeInfo SNR and Time from Packet For: \(updatedNode!.user.longName)")
}
}
@ -496,41 +481,119 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeriph
meshData.nodes.append(updatedNode!)
meshData.save()
if meshLoggingEnabled { Logger.log("Updated NodeInfo SNR and Time from Position App Packet For: \(updatedNode!.user.longName)") }
print("Updated Position SNR and Time from Packet For: \(updatedNode!.user.longName)")
}
print("Postion Payload")
print(try decodedInfo.packet.jsonString())
print("Postion Payload")
print(try decodedInfo.packet.jsonString())
}
else if decodedInfo.packet.decoded.portnum == PortNum.adminApp {
print("Admin App Packet")
print(try decodedInfo.packet.jsonString())
}
else if decodedInfo.packet.decoded.portnum == PortNum.routingApp {
print("Routing App Packet")
//print(try decodedInfo.packet.jsonString())
}
else
{
print("Other App Packet")
print(try decodedInfo.packet.jsonString())
}
else if decodedInfo.packet.decoded.portnum == PortNum.adminApp {
if meshLoggingEnabled { Logger.log("BLE FROMRADIO received for Admin App UNHANDLED \(try decodedInfo.packet.jsonString())") }
print("Admin App Packet")
print(try decodedInfo.packet.jsonString())
}
else if decodedInfo.packet.decoded.portnum == PortNum.routingApp {
if meshLoggingEnabled { Logger.log("BLE FROMRADIO received for Routing App UNHANDLED \(try decodedInfo.packet.jsonString())") }
print("Routing App Packet")
print(try decodedInfo.packet.jsonString())
}
else
{
if meshLoggingEnabled { Logger.log("BLE FROMRADIO received for Other App UNHANDLED \(try decodedInfo.packet.jsonString())") }
print("Other App Packet")
print(try decodedInfo.packet.jsonString())
}
} catch {
if meshLoggingEnabled { Logger.log("Fatal Error: Failed to decode json") }
fatalError("Failed to decode json")
}
}
if decodedInfo.configCompleteID != 0 {
print("Config Complete: \(decodedInfo)")
if meshLoggingEnabled { Logger.log("Config Complete: \(decodedInfo.configCompleteID)") }
print("BLE Config Complete Packet Id: \(decodedInfo.configCompleteID)")
}
default:
if meshLoggingEnabled { Logger.log("Unhandled Characteristic UUID: \(characteristic.uuid)") }
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
}
peripheral.readValue(for: FROMRADIO_characteristic)
}
// Send Broadcast Message
public func sendMessage(message: String) -> Bool
{
var success = false
// Return false if we are not properly connected to a device, handle retry logic in the view for now
if connectedPeripheral == nil || connectedPeripheral!.peripheral.state != CBPeripheralState.connected {
self.disconnectDevice()
self.startScanning()
// Try and connect to the preferredPeripherial first
let preferredPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? "" }).first
if preferredPeripheral != nil && preferredPeripheral?.peripheral != nil {
connectTo(peripheral: preferredPeripheral!.peripheral)
} else {
// Try and connect to the last connected device
let lastConnectedPeripheral = peripherals.filter({ $0.peripheral.identifier.uuidString == self.lastConnectedPeripheral }).first
if lastConnectedPeripheral != nil && lastConnectedPeripheral?.peripheral != nil {
connectTo(peripheral: lastConnectedPeripheral!.peripheral)
}
}
success = false
}
else if message.count < 1 {
// Don's send an empty message
success = false
}
else {
var longName: String = self.connectedPeripheral.name
var shortName: String = "???"
var nodeNum: UInt32 = self.connectedPeripheral.myInfo?.myNodeNum ?? 0
if connectedNode != nil {
longName = connectedNode.user.longName
shortName = connectedNode.user.shortName
nodeNum = connectedNode.num
}
let messageModel = MessageModel(messageId: 0, messageTimeStamp: UInt32(Date().timeIntervalSince1970), fromUserId: nodeNum, toUserId: broadcastNodeId, fromUserLongName: longName, toUserLongName: "Broadcast", fromUserShortName: shortName, toUserShortName: "BC", receivedACK: false, messagePayload: message, direction: "OUT")
let dataType = PortNum.textMessageApp
let payloadData: Data = message.data(using: String.Encoding.utf8)!
var dataMessage = DataMessage()
dataMessage.payload = payloadData
dataMessage.portnum = dataType
var meshPacket = MeshPacket()
meshPacket.to = broadcastNodeId
meshPacket.decoded = dataMessage
meshPacket.wantAck = true
var toRadio: ToRadio!
toRadio = ToRadio()
toRadio.packet = meshPacket
let binaryData: Data = try! toRadio.serializedData()
if (connectedPeripheral!.peripheral.state == CBPeripheralState.connected)
{
connectedPeripheral.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse)
messageData.messages.append(messageModel)
messageData.save()
success = true
}
}
return success
}
}

View file

@ -0,0 +1,31 @@
import Foundation
class Logger {
static var logFile: URL? {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
let fileName = "mesh.log"
return documentsDirectory.appendingPathComponent(fileName)
}
static func log(_ message: String) {
guard let logFile = logFile else {
return
}
let formatter = DateFormatter()
formatter.dateFormat = "M/d/yy h:mm:ss.SSSS"
let timestamp = formatter.string(from: Date())
guard let data = (message + " - " + timestamp + "\n").data(using: String.Encoding.utf8) else { return }
if FileManager.default.fileExists(atPath: logFile.path) {
if let fileHandle = try? FileHandle(forWritingTo: logFile) {
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
}
} else {
try? data.write(to: logFile, options: .atomicWrite)
}
}
}

View file

@ -93,8 +93,8 @@ extension NodeInfoModel {
static var data: [NodeInfoModel] {
[
NodeInfoModel(num: 2792101487, user: User(id: "!a66c166f", longName: "RAK Solar 2", shortName: "RS2", hwModel: "RAK4631"), position: Position(latitudeI:nil, longitudeI: nil, altitude: nil, batteryLevel: 68, time: nil), lastHeard: 1631593661, snr: nil),
NodeInfoModel(num: 1000569662, user: User(id: "!3ba37b3e", longName: "RAK Solar 1", shortName: "RS1", hwModel: "RAK4631"), position: Position(latitudeI:476021390, longitudeI: -1221532609, altitude: 71, batteryLevel: 70, time: 1632202227), lastHeard: 1632202227, snr: 5.25)
// NodeInfoModel(num: 2792101487, user: User(id: "!a66c166f", longName: "RAK Solar 2", shortName: "RS2", hwModel: "RAK4631"), position: Position(latitudeI:nil, longitudeI: nil, altitude: nil, batteryLevel: 68, time: nil), lastHeard: 1631593661, snr: nil),
// NodeInfoModel(num: 1000569662, user: User(id: "!3ba37b3e", longName: "RAK Solar 1", shortName: "RS1", hwModel: "RAK4631"), position: Position(latitudeI:476021390, longitudeI: -1221532609, altitude: 71, batteryLevel: 70, time: 1632202227), lastHeard: 1632202227, snr: 5.25)
]
}
}

View file

@ -33,53 +33,53 @@ struct Connect: View {
Section(header: Text("Connected Device").font(.title)) {
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)
if bleManager.connectedPeripheral.myInfo != nil {
VStack (alignment: .leading) {
if bleManager.connectedNode != nil {
Text(bleManager.connectedNode.user.longName).font(.title2)
}
else {
Text(String(bleManager.connectedPeripheral.myInfo?.myNodeNum ?? 0)).font(.title2)
}
Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.myInfo?.firmwareVersion ?? "(null)").font(.caption).foregroundColor(Color.gray)
}
Spacer()
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 {
if bleManager.connectedNode != nil {
userSettings.preferredPeripheralName = "\(bleManager.connectedNode.user.longName) / \(bleManager.connectedPeripheral.peripheral.name ?? "")"
}
else {
userSettings.preferredPeripheralName = bleManager.connectedPeripheral.peripheral.name ?? "Unknown Device"
}
userSettings.preferredPeripheralId = bleManager.connectedPeripheral!.peripheral.identifier.uuidString
} else {
userSettings.preferredPeripheralId = ""
userSettings.preferredPeripheralName = ""
}
}
VStack (alignment: .leading) {
if bleManager.connectedNode != nil {
Text(bleManager.connectedNode.user.longName).font(.title2)
}
}
else {
Text((bleManager.connectedPeripheral!.peripheral.name != nil) ? bleManager.connectedPeripheral!.peripheral.name! : "Unknown").font(.title2)
}
else {
Text(String(bleManager.connectedPeripheral.peripheral.name ?? "Unknown")).font(.title2)
}
Text("Model: ").font(.caption)+Text(bleManager.connectedNode?.user.hwModel ?? "(null)").font(.caption).foregroundColor(Color.gray)
if bleManager.connectedNode != nil {
Text("BLE Name: ").font(.caption)+Text(bleManager.connectedPeripheral.name).font(.caption).foregroundColor(Color.gray)
}
Text("FW Version: ").font(.caption)+Text(bleManager.connectedPeripheral.myInfo?.firmwareVersion ?? "(null)").font(.caption).foregroundColor(Color.gray)
}
Spacer()
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 {
if bleManager.connectedNode != nil {
let deviceName = "\(bleManager.connectedNode.user.longName) / \(bleManager.connectedPeripheral.peripheral.name ?? "")"
UserDefaults.standard.set(deviceName, forKey: "preferredPeripheralName")
//userSettings.preferredPeripheralName = "\(bleManager.connectedNode.user.longName) / \(bleManager.connectedPeripheral.peripheral.name ?? "")"
} else {
UserDefaults.standard.set(bleManager.connectedPeripheral.peripheral.name ?? "Unknown Device", forKey: "preferredPeripheralName")
//userSettings.preferredPeripheralName =
}
UserDefaults.standard.set(bleManager.connectedPeripheral!.peripheral.identifier.uuidString, forKey: "preferredPeripheralId")
} else {
UserDefaults.standard.set("", forKey: "preferredPeripheralId")
UserDefaults.standard.set("", forKey: "preferredPeripheralName")
}
}
}
}
.padding([.top, .bottom])
.swipeActions {
@ -193,7 +193,9 @@ struct Connect: View {
}
else {
isPreferredRadio = false
bleManager.startScanning()
if bleManager.connectedPeripheral == nil {
bleManager.startScanning()
}
}
} )
}

View file

@ -3,17 +3,14 @@ import SwiftUI
import CoreBluetooth
struct Channels: View {
// Message Data and Bluetooth
@EnvironmentObject var messageData: MessageData
@EnvironmentObject var bleManager: BLEManager
@State private var isShowingDetailView = true
var body: some View {
NavigationView {
GeometryReader { bounds in
NavigationLink(destination: Messages()) {
NavigationLink(destination: Messages(), isActive: $isShowingDetailView) {
List{

View file

@ -9,9 +9,6 @@ struct Messages: View {
case messageText
}
@ObservedObject var userSettings = UserSettings()
// Keyboard State
@State var typingMessage: String = ""
@State private var totalBytes = 0
@ -119,7 +116,7 @@ struct Messages: View {
ZStack {
//let kbType = Enum.Parse(typeof(KeyboardType), userSettings.keyboardType, true);
let kbType = UIKeyboardType(rawValue: userSettings.keyboardType)
let kbType = UIKeyboardType(rawValue: UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0)
TextEditor(text: $typingMessage)
.onChange(of: typingMessage, perform: { value in

View file

@ -78,9 +78,6 @@ struct NodeList: View {
}
.ignoresSafeArea(.all, edges: [.leading, .trailing])
.navigationViewStyle(DoubleColumnNavigationViewStyle())
.onAppear{
meshData.load()
}
}
}

View file

@ -56,6 +56,11 @@ class UserSettings: ObservableObject {
UserDefaults.standard.set(keyboardType, forKey: "keyboardType")
}
}
@Published var meshActivityLog: Bool {
didSet {
UserDefaults.standard.set(meshActivityLog, forKey: "meshActivityLog")
}
}
init() {
self.username = UserDefaults.standard.object(forKey: "username") as? String ?? ""
@ -63,6 +68,7 @@ class UserSettings: ObservableObject {
self.preferredPeripheralId = UserDefaults.standard.object(forKey: "preferredPeripheralId") as? String ?? ""
self.provideLocation = UserDefaults.standard.object(forKey: "provideLocation") as? Bool ?? false
self.keyboardType = UserDefaults.standard.object(forKey: "keyboardType") as? Int ?? 0
self.meshActivityLog = UserDefaults.standard.object(forKey: "meshActivityLog") as? Bool ?? false
}
}
@ -72,6 +78,10 @@ struct AppSettings: View {
@EnvironmentObject var bleManager: BLEManager
@ObservedObject var userSettings = UserSettings()
var perferredPeripheral: String {
UserDefaults.standard.object(forKey: "preferredPeripheralName") as? String ?? ""
}
var body: some View {
NavigationView {
@ -115,11 +125,26 @@ struct AppSettings: View {
}
.pickerStyle(DefaultPickerStyle())
}
Section(header: Text("MESH NETWORK OPTIONS")) {
Toggle(isOn: $userSettings.meshActivityLog) {
Label("Log all Mesh activity", systemImage: "network")
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
if userSettings.meshActivityLog {
NavigationLink(destination: MeshLog()) {
Text("View Mesh Log")
}
.listRowSeparator(.visible)
}
}
}
}
.navigationTitle("App Settings")
}
.navigationViewStyle(StackNavigationViewStyle()) }
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct AppSettings_Previews: PreviewProvider {

View file

@ -0,0 +1,70 @@
import SwiftUI
import Foundation
struct MeshLog: View {
let logFile = Logger.logFile
var text = ""
@State private var logs = [String]()
var body: some View {
List(logs, id: \.self, rowContent: Text.init)
.task {
do {
let url = logFile!
logs.removeAll()
for try await log in url.lines {
logs.append(log)
}
logs.reverse()
} catch {
// Stop adding logs when an error is thrown
}
}
.textSelection(.enabled)
.font(.caption2)
HStack (alignment: .center) {
Spacer()
Button(action: {
let text = ""
do {
try text.write(to: logFile!, atomically: false, encoding: .utf8)
logs.removeAll()
} catch {
print(error)
}
}) {
Image(systemName: "trash").imageScale(.large).foregroundColor(.gray)
Text("Clear Log").font(.caption)
.font(.caption)
.foregroundColor(.gray)
}
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
Spacer()
Button(action: {
}) {
Image(systemName: "arrow.down.circle.fill").imageScale(.large).foregroundColor(.gray)
Text("Download Log")
.font(.caption)
.foregroundColor(.gray)
}
.padding()
.background(Color(.systemGray6))
.clipShape(Capsule())
.hidden()
Spacer()
}
.padding(.bottom, 10)
.navigationTitle("Mesh Activity Log")
}
}