mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
V 1.26.4 Mesh Logging and automatically select messages
This commit is contained in:
parent
c10e70491e
commit
d447aa1ca7
10 changed files with 335 additions and 145 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
31
MeshtasticClient/Helpers/Logger.swift
Normal file
31
MeshtasticClient/Helpers/Logger.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
} )
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -78,9 +78,6 @@ struct NodeList: View {
|
|||
}
|
||||
.ignoresSafeArea(.all, edges: [.leading, .trailing])
|
||||
.navigationViewStyle(DoubleColumnNavigationViewStyle())
|
||||
.onAppear{
|
||||
meshData.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
70
MeshtasticClient/Views/Settings/MeshLog.swift
Normal file
70
MeshtasticClient/Views/Settings/MeshLog.swift
Normal 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")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue