From e17791b71d79709953d67314a147ee859f31fa1b Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Thu, 12 Jun 2025 12:06:23 -0700 Subject: [PATCH 1/4] First get config to subscribe then fetch nodes --- Meshtastic/Helpers/BLEManager.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index c1dc7023..008fe3e8 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -511,7 +511,10 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.mesh.info("πŸ›ŽοΈ \(logString, privacy: .public)") // BLE Characteristics discovered, issue wantConfig var toRadio: ToRadio = ToRadio() - configNonce += 1 + configNonce = UInt32(69421) + if !isSubscribed { + configNonce = UInt32(69420) // Get config first + } toRadio.wantConfigID = configNonce guard let binaryData: Data = try? toRadio.serializedData() else { return @@ -982,7 +985,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.mesh.warning("πŸ•ΈοΈ MESH PACKET received for Key Verification App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") } - if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == configNonce { + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == 69420 { invalidVersion = false lastConnectionError = "" isSubscribed = true @@ -1022,6 +1025,12 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } catch { Logger.data.error("Failed to find a node info for the connected node \(error.localizedDescription, privacy: .public)") } + Logger.mesh.info("🀜 [BLE] Want Config Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)") + sendWantConfig() + + } + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == 69421 { + Logger.mesh.info("🀜 [BLE] Want Config DB Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)") } // MARK: Share Location Position Update Timer From 4d6e8c8940cccea52dfb2e2534d3361b9d42a849 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:58:17 -0700 Subject: [PATCH 2/4] fixed some logic --- Meshtastic/Helpers/BLEManager.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 008fe3e8..bc5fd6b3 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -1029,9 +1029,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate sendWantConfig() } - if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == 69421 { - Logger.mesh.info("🀜 [BLE] Want Config DB Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)") - } + // MARK: Share Location Position Update Timer // Use context to pass the radio name with the timer @@ -1045,6 +1043,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return } + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == 69421 { + Logger.mesh.info("🀜 [BLE] Want Config DB Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)") + } case FROMNUM_UUID: Logger.services.info("πŸ—žοΈ [BLE] (Notify) characteristic value will be read next") From 5a854725ee53233ae8478432c8a433a936976cd5 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sat, 14 Jun 2025 09:10:07 -0700 Subject: [PATCH 3/4] added nonce variables --- Meshtastic/Helpers/BLEManager.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index bc5fd6b3..4c093547 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -52,6 +52,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let FROMNUM_UUID = CBUUID(string: "0xED9DA18C-A800-4F66-A670-AA7547E34453") let LEGACY_LOGRADIO_UUID = CBUUID(string: "0x6C6FD238-78FA-436B-AACF-15C5BE1EF2E2") let LOGRADIO_UUID = CBUUID(string: "0x5a3d6e49-06e6-4423-9944-e9de8cdf9547") + + let NONCE_ONLY_CONFIG = 69420 + let NONCE_ONLY_DB = 69421 // MARK: init private override init() { @@ -511,9 +514,9 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.mesh.info("πŸ›ŽοΈ \(logString, privacy: .public)") // BLE Characteristics discovered, issue wantConfig var toRadio: ToRadio = ToRadio() - configNonce = UInt32(69421) + configNonce = UInt32(NONCE_ONLY_DB) if !isSubscribed { - configNonce = UInt32(69420) // Get config first + configNonce = UInt32(NONCE_ONLY_CONFIG) // Get config first } toRadio.wantConfigID = configNonce guard let binaryData: Data = try? toRadio.serializedData() else { @@ -985,7 +988,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate Logger.mesh.warning("πŸ•ΈοΈ MESH PACKET received for Key Verification App UNHANDLED \((try? decodedInfo.packet.jsonString()) ?? "JSON Decode Failure", privacy: .public)") } - if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == 69420 { + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == NONCE_ONLY_CONFIG { invalidVersion = false lastConnectionError = "" isSubscribed = true @@ -1043,7 +1046,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return } - if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == 69421 { + if decodedInfo.configCompleteID != 0 && decodedInfo.configCompleteID == NONCE_ONLY_DB { Logger.mesh.info("🀜 [BLE] Want Config DB Complete. ID:\(decodedInfo.configCompleteID, privacy: .public)") } From 5746d2df7a9dff5c87ddd4cabe2f0bb6e376d3f6 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sat, 14 Jun 2025 15:13:18 -0700 Subject: [PATCH 4/4] Added retry timeout --- Meshtastic/Helpers/BLEManager.swift | 122 ++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 26 deletions(-) diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 4c093547..b93d0264 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -55,6 +55,13 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate let NONCE_ONLY_CONFIG = 69420 let NONCE_ONLY_DB = 69421 + private var isWaitingForWantConfigResponse = false + + private var wantConfigTimer: Timer? + private var wantConfigRetryCount = 0 + private let maxWantConfigRetries = 3 + private let wantConfigTimeoutInterval: TimeInterval = 5.0 + // MARK: init private override init() { @@ -499,35 +506,96 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } return success } + + func sendWantConfig() { + isWaitingForWantConfigResponse = true + + guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return } + + if FROMRADIO_characteristic == nil { + Logger.mesh.error("🚨 \("Unsupported Firmware Version Detected, unable to connect to device.".localized, privacy: .public)") + invalidVersion = true + return + } else { + + let nodeName = connectedPeripheral?.peripheral.name ?? "Unknown".localized + let logString = String.localizedStringWithFormat("Issuing Want Config to %@".localized, nodeName) + Logger.mesh.info("πŸ›ŽοΈ \(logString, privacy: .public)") + + // BLE Characteristics discovered, issue wantConfig + var toRadio: ToRadio = ToRadio() + configNonce = UInt32(NONCE_ONLY_DB) + if !isSubscribed { + configNonce = UInt32(NONCE_ONLY_CONFIG) // Get config first + } + toRadio.wantConfigID = configNonce + guard let binaryData: Data = try? toRadio.serializedData() else { + return + } + connectedPeripheral!.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) + + // Either Read the config complete value or from num notify value + guard connectedPeripheral != nil else { return } + connectedPeripheral!.peripheral.readValue(for: FROMRADIO_characteristic) + + // Start timeout timer + startWantConfigTimeout() + } + } - func sendWantConfig() { - guard connectedPeripheral?.peripheral.state ?? CBPeripheralState.disconnected == CBPeripheralState.connected else { return } + private func startWantConfigTimeout() { + // Cancel any existing timer + wantConfigTimer?.invalidate() + + // Start new timer + wantConfigTimer = Timer.scheduledTimer(withTimeInterval: wantConfigTimeoutInterval, repeats: false) { [weak self] _ in + self?.handleWantConfigTimeout() + } + } - if FROMRADIO_characteristic == nil { - Logger.mesh.error("🚨 \("Unsupported Firmware Version Detected, unable to connect to device.".localized, privacy: .public)") - invalidVersion = true - return - } else { + private func handleWantConfigTimeout() { + guard isWaitingForWantConfigResponse else { return } + + wantConfigRetryCount += 1 + + if wantConfigRetryCount < maxWantConfigRetries { + Logger.mesh.warning("⏰ Want Config timeout, retrying... (attempt \(self.wantConfigRetryCount + 1)/\(self.maxWantConfigRetries))") + sendWantConfig() + } else { + Logger.mesh.error("🚨 Want Config failed after \(self.maxWantConfigRetries) attempts, forcing disconnect") + forceDisconnect() + } + } - let nodeName = connectedPeripheral?.peripheral.name ?? "Unknown".localized - let logString = String.localizedStringWithFormat("Issuing Want Config to %@".localized, nodeName) - Logger.mesh.info("πŸ›ŽοΈ \(logString, privacy: .public)") - // BLE Characteristics discovered, issue wantConfig - var toRadio: ToRadio = ToRadio() - configNonce = UInt32(NONCE_ONLY_DB) - if !isSubscribed { - configNonce = UInt32(NONCE_ONLY_CONFIG) // Get config first - } - toRadio.wantConfigID = configNonce - guard let binaryData: Data = try? toRadio.serializedData() else { - return - } - connectedPeripheral!.peripheral.writeValue(binaryData, for: TORADIO_characteristic, type: .withResponse) - // Either Read the config complete value or from num notify value - guard connectedPeripheral != nil else { return } - connectedPeripheral!.peripheral.readValue(for: FROMRADIO_characteristic) - } - } + func onWantConfigResponseReceived() { + if isWaitingForWantConfigResponse { + isWaitingForWantConfigResponse = false + wantConfigTimer?.invalidate() + wantConfigTimer = nil + wantConfigRetryCount = 0 // Reset retry count on success + } + } + + private func forceDisconnect() { + isWaitingForWantConfigResponse = false + wantConfigTimer?.invalidate() + wantConfigTimer = nil + wantConfigRetryCount = 0 + + disconnectPeripheral(reconnect: false) + + lastConnectionError = "Want Config timeout" + + Logger.mesh.info("πŸ”Œ Forced disconnect due to Want Config timeout") + } + + // Call this to reset the retry mechanism (e.g., on new connection) + func resetWantConfigRetries() { + wantConfigRetryCount = 0 + wantConfigTimer?.invalidate() + wantConfigTimer = nil + isWaitingForWantConfigResponse = false + } func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { if let error { @@ -700,6 +768,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } // NodeInfo if decodedInfo.nodeInfo.num > 0 { + onWantConfigResponseReceived() nowKnown = true if let nodeInfo = nodeInfoPacket(nodeInfo: decodedInfo.nodeInfo, channel: decodedInfo.packet.channel, context: context) { if self.connectedPeripheral != nil && self.connectedPeripheral.num == nodeInfo.num { @@ -722,6 +791,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate } // Module Config if decodedInfo.moduleConfig.isInitialized && !invalidVersion && self.connectedPeripheral?.num != 0 { + onWantConfigResponseReceived() nowKnown = true moduleConfig(config: decodedInfo.moduleConfig, context: context, nodeNum: Int64(truncatingIfNeeded: self.connectedPeripheral?.num ?? 0), nodeLongName: self.connectedPeripheral.longName) if decodedInfo.moduleConfig.payloadVariant == ModuleConfig.OneOf_PayloadVariant.cannedMessage(decodedInfo.moduleConfig.cannedMessage) {