diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 66cb348e..0749e8d6 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 25AECD4F2C2F723200862C8E /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 25AECD4E2C2F723200862C8E /* Localizable.xcstrings */; }; 25F26B1E2C2F610D00C9CD9D /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD5BB0C2C285F00007E03CA /* Logger.swift */; }; 25F26B1F2C2F611300C9CD9D /* AppData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD5BB152C28B1E4007E03CA /* AppData.swift */; }; + 6D825E622C34786C008DBEE4 /* CommonRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D825E612C34786C008DBEE4 /* CommonRegex.swift */; }; 6DA39D8E2A92DC52007E311C /* MeshtasticAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */; }; 6DEDA55A2A957B8E00321D2E /* DetectionSensorLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */; }; 6DEDA55C2A9592F900321D2E /* MessageEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */; }; @@ -222,6 +223,7 @@ /* Begin PBXFileReference section */ 25AECD4E2C2F723200862C8E /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + 6D825E612C34786C008DBEE4 /* CommonRegex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonRegex.swift; sourceTree = ""; }; 6DA39D8D2A92DC52007E311C /* MeshtasticAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeshtasticAppDelegate.swift; sourceTree = ""; }; 6DEDA5592A957B8E00321D2E /* DetectionSensorLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectionSensorLog.swift; sourceTree = ""; }; 6DEDA55B2A9592F900321D2E /* MessageEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageEntityExtension.swift; sourceTree = ""; }; @@ -832,6 +834,7 @@ DDA6B2E828419CF2003E8C16 /* MeshPackets.swift */, DD964FBC296E6B01007C176F /* EmojiOnlyTextField.swift */, DD3619142B1EF9F900C41C8C /* LocationsHandler.swift */, + 6D825E612C34786C008DBEE4 /* CommonRegex.swift */, ); path = Helpers; sourceTree = ""; @@ -1099,6 +1102,7 @@ DD798B072915928D005217CD /* ChannelMessageList.swift in Sources */, DDC2E1A726CEB3400042C5E4 /* LocationHelper.swift in Sources */, DD77093D2AA1AFA3007A8BF0 /* ChannelTips.swift in Sources */, + 6D825E622C34786C008DBEE4 /* CommonRegex.swift in Sources */, DD913639270DFF4C00D7ACF3 /* LocalNotificationManager.swift in Sources */, DDDB444C29F8AAA600EE2349 /* Color.swift in Sources */, DDB8F4122A9EE5DD00230ECE /* UserList.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index f3718afc..d19a3124 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -6,7 +6,6 @@ import MapKit import MeshtasticProtobufs import CocoaMQTT import OSLog -import RegexBuilder // --------------------------------------------------------------------------------------- // Meshtastic BLE Device Manager @@ -43,13 +42,15 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate var TORADIO_characteristic: CBCharacteristic! var FROMRADIO_characteristic: CBCharacteristic! var FROMNUM_characteristic: CBCharacteristic! + var LEGACY_LOGRADIO_characteristic: CBCharacteristic! var LOGRADIO_characteristic: CBCharacteristic! let meshtasticServiceCBUUID = CBUUID(string: "0x6BA1B218-15A8-461F-9FA8-5DCAE273EAFD") let TORADIO_UUID = CBUUID(string: "0xF75C76D2-129E-4DAD-A1DD-7866124401E7") let FROMRADIO_UUID = CBUUID(string: "0x2C55E69E-4993-11ED-B878-0242AC120002") let EOL_FROMRADIO_UUID = CBUUID(string: "0x8BA2BCC2-EE02-4A55-A531-C525C5E454D5") let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453") - let LOGRADIO_UUID = CBUUID(string: "0x6C6FD238-78FA-436B-AACF-15C5BE1EF2E2") + let LEGACY_LOGRADIO_UUID = CBUUID(string: "0x6C6FD238-78FA-436B-AACF-15C5BE1EF2E2") + let LOGRADIO_UUID = CBUUID(string: "0x5a3d6e49-06e6-4423-9944-e9de8cdf9547") // MARK: init BLEManager override init() { @@ -281,7 +282,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } guard let services = peripheral.services else { return } for service in services where service.uuid == meshtasticServiceCBUUID { - peripheral.discoverCharacteristics([TORADIO_UUID, FROMRADIO_UUID, FROMNUM_UUID, LOGRADIO_UUID], for: service) + peripheral.discoverCharacteristics([TORADIO_UUID, FROMRADIO_UUID, FROMNUM_UUID, LEGACY_LOGRADIO_UUID, LOGRADIO_UUID], for: service) Logger.services.info("โœ… [BLE] Service for Meshtastic discovered by \(peripheral.name ?? "Unknown", privacy: .public)") } } @@ -315,6 +316,11 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate FROMNUM_characteristic = characteristic peripheral.setNotifyValue(true, for: characteristic) + case LEGACY_LOGRADIO_UUID: + Logger.services.info("โœ… [BLE] did discover legacy LOGRADIO (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown", privacy: .public)") + LEGACY_LOGRADIO_characteristic = characteristic + peripheral.setNotifyValue(true, for: characteristic) + case LOGRADIO_UUID: Logger.services.info("โœ… [BLE] did discover LOGRADIO (Notify) characteristic for Meshtastic by \(peripheral.name ?? "Unknown", privacy: .public)") LOGRADIO_characteristic = characteristic @@ -506,7 +512,54 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } } - // MARK: Data Read / Update Characteristic Event + fileprivate func handleRadioLog(radioLog: String) { + var log = radioLog + /// Debug Log Level + if log.starts(with: "DEBUG |") { + do { + let logString = log + if let coordsMatch = try CommonRegex.COORDS_REGEX.firstMatch(in: logString) { + log = "\(log.replacingOccurrences(of: "DEBUG |", with: "").trimmingCharacters(in: .whitespaces))" + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.debug("๐Ÿ›ฐ๏ธ \(log.prefix(upTo: coordsMatch.range.lowerBound), privacy: .public) \(coordsMatch.0.replacingOccurrences(of: "[,]", with: "", options: .regularExpression), privacy: .private) \(log.suffix(from: coordsMatch.range.upperBound), privacy: .public)") + } else { + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.debug("๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ \(log.replacingOccurrences(of: "DEBUG |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") + } + } catch { + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.debug("๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ \(log.replacingOccurrences(of: "DEBUG |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") + } + } else if log.starts(with: "INFO |") { + do { + let logString = log + if let coordsMatch = try CommonRegex.COORDS_REGEX.firstMatch(in: logString) { + log = "\(log.replacingOccurrences(of: "INFO |", with: "").trimmingCharacters(in: .whitespaces))" + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.info("๐Ÿ›ฐ๏ธ \(log.prefix(upTo: coordsMatch.range.lowerBound), privacy: .public) \(coordsMatch.0.replacingOccurrences(of: "[,]", with: "", options: .regularExpression), privacy: .private) \(log.suffix(from: coordsMatch.range.upperBound), privacy: .public)") + } else { + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.info("๐Ÿ“ข \(log.replacingOccurrences(of: "INFO |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") + } + } catch { + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.info("๐Ÿ“ข \(log.replacingOccurrences(of: "INFO |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") + } + } else if log.starts(with: "WARN |") { + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.warning("โš ๏ธ \(log.replacingOccurrences(of: "WARN |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") + } else if log.starts(with: "ERROR |") { + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.error("๐Ÿ’ฅ \(log.replacingOccurrences(of: "ERROR |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") + } else if log.starts(with: "CRIT |") { + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.critical("๐Ÿงจ \(log.replacingOccurrences(of: "CRIT |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") + } else { + log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) + Logger.radio.debug("๐Ÿ“Ÿ \(log, privacy: .public)") + } + } + func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { if let error { @@ -529,67 +582,35 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate if characteristic.value == nil || characteristic.value!.isEmpty { return } - let coordsSearch = Regex { - Capture { - Regex { - "lat=" - OneOrMore(.digit) - } - } - Capture {" "} - Capture { - Regex { - "long=" - OneOrMore(.digit) - } + do { + let logRecord = try LogRecord(serializedData: characteristic.value!) + var message = logRecord.source.isEmpty ? logRecord.message : "[\(logRecord.source)] \(logRecord.message)" + switch logRecord.level { + case .debug: + message = "DEBUG | \(message)" + case .info: + message = "INFO | \(message)" + case .warning: + message = "WARN | \(message)" + case .error: + message = "ERROR | \(message)" + case .critical: + message = "CRIT | \(message)" + default: + message = "DEBUG | \(message)" } + handleRadioLog(radioLog: message) } - .anchorsMatchLineEndings() - if var log = String(data: characteristic.value!, encoding: .utf8) { - /// Debug Log Level - if log.starts(with: "DEBUG |") { - do { - let logString = log - if let coordsMatch = try coordsSearch.firstMatch(in: logString) { - log = "\(log.replacingOccurrences(of: "DEBUG |", with: "").trimmingCharacters(in: .whitespaces))" - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.debug("๐Ÿ›ฐ๏ธ \(log.prefix(upTo: coordsMatch.range.lowerBound), privacy: .public) \(coordsMatch.0.replacingOccurrences(of: "[,]", with: "", options: .regularExpression), privacy: .private) \(log.suffix(from: coordsMatch.range.upperBound), privacy: .public)") - } else { - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.debug("๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ \(log.replacingOccurrences(of: "DEBUG |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") - } - } catch { - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.debug("๐Ÿ•ต๐Ÿปโ€โ™‚๏ธ \(log.replacingOccurrences(of: "DEBUG |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") - } - } else if log.starts(with: "INFO |") { - do { - let logString = log - if let coordsMatch = try coordsSearch.firstMatch(in: logString) { - log = "\(log.replacingOccurrences(of: "INFO |", with: "").trimmingCharacters(in: .whitespaces))" - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.info("๐Ÿ›ฐ๏ธ \(log.prefix(upTo: coordsMatch.range.lowerBound), privacy: .public) \(coordsMatch.0.replacingOccurrences(of: "[,]", with: "", options: .regularExpression), privacy: .private) \(log.suffix(from: coordsMatch.range.upperBound), privacy: .public)") - } else { - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.info("๐Ÿ“ข \(log.replacingOccurrences(of: "INFO |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") - } - } catch { - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.info("๐Ÿ“ข \(log.replacingOccurrences(of: "INFO |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") - } - } else if log.starts(with: "WARN |") { - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.warning("โš ๏ธ \(log.replacingOccurrences(of: "WARN |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") - } else if log.starts(with: "ERROR |") { - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.error("๐Ÿ’ฅ \(log.replacingOccurrences(of: "ERROR |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") - } else if log.starts(with: "CRIT |") { - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.critical("๐Ÿงจ \(log.replacingOccurrences(of: "CRIT |", with: "").trimmingCharacters(in: .whitespaces), privacy: .public)") - } else { - log = log.replacingOccurrences(of: "[,]", with: "", options: .regularExpression) - Logger.radio.debug("๐Ÿ“Ÿ \(log, privacy: .public)") - } + catch { + // Ignore fail to parse as LogRecord + } + + case LEGACY_LOGRADIO_UUID: + if characteristic.value == nil || characteristic.value!.isEmpty { + return + } + if let log = String(data: characteristic.value!, encoding: .utf8) { + handleRadioLog(radioLog: log) } case FROMRADIO_UUID: diff --git a/Meshtastic/Helpers/CommonRegex.swift b/Meshtastic/Helpers/CommonRegex.swift new file mode 100644 index 00000000..683f8fd7 --- /dev/null +++ b/Meshtastic/Helpers/CommonRegex.swift @@ -0,0 +1,29 @@ +// +// CommonRegex.swift +// Meshtastic +// +// Created by Ben Meadors on 7/2/24. +// + +import Foundation +import RegexBuilder + +class CommonRegex +{ + static let COORDS_REGEX = Regex { + Capture { + Regex { + "lat=" + OneOrMore(.digit) + } + } + Capture {" "} + Capture { + Regex { + "long=" + OneOrMore(.digit) + } + } + } + .anchorsMatchLineEndings() +}