From a11d41c1372660f33e3ca25eaa251728858bea18 Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Tue, 1 Aug 2023 22:28:02 -0700 Subject: [PATCH] Setup MQTT client proxy connection --- Meshtastic.xcodeproj/project.pbxproj | 12 ++ Meshtastic/Helpers/BLEManager.swift | 18 ++ .../Helpers/Mqtt/MqttClientProxyManager.swift | 190 ++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index 89b0f090..40a144f0 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -121,6 +121,7 @@ DDCDC6CB29481FCC004C1DDA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DDCDC6CD29481FCC004C1DDA /* Localizable.strings */; }; DDCE4E2C2869F92900BE9F8F /* UserConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */; }; DDD3BBD5292D763200D609B3 /* MeshtasticTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */; }; + DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */; }; DDD6EEAF29BC024700383354 /* Firmware.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD6EEAE29BC024700383354 /* Firmware.swift */; }; DDD94A502845C8F5004A87A0 /* DateTimeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */; }; DDD9E4E4284B208E003777C5 /* UserEntityExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */; }; @@ -323,6 +324,7 @@ DDCDC6CE294821AD004C1DDA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; DDCE4E2B2869F92900BE9F8F /* UserConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConfig.swift; sourceTree = ""; }; DDD3BBD4292D763200D609B3 /* MeshtasticTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeshtasticTests.swift; sourceTree = ""; }; + DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MqttClientProxyManager.swift; sourceTree = ""; }; DDD6EEAE29BC024700383354 /* Firmware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Firmware.swift; sourceTree = ""; }; DDD94A4F2845C8F5004A87A0 /* DateTimeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeText.swift; sourceTree = ""; }; DDD9E4E3284B208E003777C5 /* UserEntityExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserEntityExtension.swift; sourceTree = ""; }; @@ -710,6 +712,7 @@ DDC2E1A526CEB32B0042C5E4 /* Helpers */ = { isa = PBXGroup; children = ( + DDD43FE12A78C86B0083A3E9 /* Mqtt */, DDB75A122A0593CD006ED576 /* Map */, DDAF8C5226EB1DF10058C060 /* BLEManager.swift */, DDC2E1A626CEB3400042C5E4 /* LocationHelper.swift */, @@ -736,6 +739,14 @@ path = Persistence; sourceTree = ""; }; + DDD43FE12A78C86B0083A3E9 /* Mqtt */ = { + isa = PBXGroup; + children = ( + DDD43FE22A78C8900083A3E9 /* MqttClientProxyManager.swift */, + ); + path = Mqtt; + sourceTree = ""; + }; DDDB443E29F79A9400EE2349 /* Extensions */ = { isa = PBXGroup; children = ( @@ -1032,6 +1043,7 @@ DD3CC6C228EB9D4900FA9159 /* UpdateCoreData.swift in Sources */, DDE0F7C5295F77B700B8AAB3 /* AppSettingsEnums.swift in Sources */, DDB6ABE628B1406100384BA1 /* LoraConfigEnums.swift in Sources */, + DDD43FE32A78C8900083A3E9 /* MqttClientProxyManager.swift in Sources */, DDDB444629F8A96500EE2349 /* Character.swift in Sources */, DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */, DDB6ABDB28B0AC6000384BA1 /* DistanceText.swift in Sources */, diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 8830ce33..3c9f86d4 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -3,6 +3,7 @@ import CoreData import CoreBluetooth import SwiftUI import MapKit +import CocoaMQTT // --------------------------------------------------------------------------------------- // Meshtastic BLE Device Manager @@ -37,6 +38,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { var positionTimer: Timer? var lastPosition: CLLocationCoordinate2D? let emptyNodeNum: UInt32 = 4294967295 + let mqttManager = MqttClientProxyManager.shared /* Meshtastic Service Details */ var TORADIO_characteristic: CBCharacteristic! var FROMRADIO_characteristic: CBCharacteristic! @@ -573,6 +575,22 @@ class BLEManager: NSObject, CBPeripheralDelegate, ObservableObject { RunLoop.current.add(positionTimer!, forMode: .common) } } + /// MQTT Client Proxy + if true { + + let fetchNodeInfoRequest: NSFetchRequest = NSFetchRequest.init(entityName: "NodeInfoEntity") + fetchNodeInfoRequest.predicate = NSPredicate(format: "num == %lld", Int64(connectedPeripheral.num)) + do { + let fetchedNodeInfo = try context?.fetch(fetchNodeInfoRequest) as? [NodeInfoEntity] ?? [] + if fetchedNodeInfo.count == 1 && fetchedNodeInfo[0].mqttConfig != nil { + //Subscribe to Mqtt Client Proxy if enabled + mqttManager.connectFromConfigSettings(config: fetchedNodeInfo[0].mqttConfig!) + + } + } catch { + print("Failed to find a node info for the connected node") + } + } return } diff --git a/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift new file mode 100644 index 00000000..d9422e60 --- /dev/null +++ b/Meshtastic/Helpers/Mqtt/MqttClientProxyManager.swift @@ -0,0 +1,190 @@ +// +// MQTTManager.swift +// Meshtastic +// +// Created by Garth Vander Houwen on 7/31/23. +// + +import Foundation +import CocoaMQTT + +class MqttClientProxyManager { + + enum ConnectionStatus { + case connecting + case connected + case disconnecting + case disconnected + case error + case none + } + + enum MqttQos: Int { + case atMostOnce = 0 + case atLeastOnce = 1 + case exactlyOnce = 2 + } + + static let shared = MqttClientProxyManager() + private static let defaultKeepAliveInterval: Int32 = 60 + weak var delegate: CocoaMQTTDelegate? + var status = ConnectionStatus.none + var mqttClient: CocoaMQTT? + + private init() { + + } + + func connectFromConfigSettings(config: MQTTConfigEntity) { + + let defaultServerAddress = "mqtt.meshtastic.org" + let defaultServerPort = 1883 + //let + var host = config.address + if host == nil || host!.isEmpty { + host = defaultServerAddress + } + + if let host = host { + let port = defaultServerPort + let username = config.username + let password = config.password + connect(host: host, port: port, username: username, password: password, cleanSession: true) + } + } + + func connect(host: String, port: Int, username: String?, password: String?, cleanSession: Bool) { + + let clientId = "MeshtasticAppleMqttProxy-" + String(ProcessInfo().processIdentifier) + status = .connecting + mqttClient = CocoaMQTT(clientID: clientId, host: host, port: UInt16(port)) + + if let mqttClient = mqttClient { + + mqttClient.username = username + mqttClient.password = password + mqttClient.keepAlive = 60 + mqttClient.cleanSession = cleanSession + mqttClient.logLevel = .debug + mqttClient.willMessage = CocoaMQTTMessage(topic: "/will", string: "dieout") + mqttClient.autoReconnect = true + + mqttClient.delegate = self + let success = mqttClient.connect() + if !success { + status = .error + } + + } else { + status = .error + } + } + + func subscribe(topic: String, qos: MqttQos) { + let qos = CocoaMQTTQoS(rawValue :UInt8(qos.rawValue))! + mqttClient?.subscribe(topic, qos: qos) + } + + func unsubscribe(topic: String) { + mqttClient?.unsubscribe(topic) + } + + func publish(message: String, topic: String, qos: MqttQos) { + let qos = CocoaMQTTQoS(rawValue :UInt8(qos.rawValue))! + mqttClient?.publish(topic, withString: message, qos: qos) + } + + func disconnect() { + //MqttSettings.shared.isConnected = false + + if let client = mqttClient { + status = .disconnecting + client.disconnect() + } else { + status = .disconnected + } + } +} + +extension MqttClientProxyManager: CocoaMQTTDelegate { + + func mqtt(_ mqtt: CocoaMQTT, didReceive trust: SecTrust, completionHandler: @escaping (Bool) -> Void) { + completionHandler(true) + } + + func mqtt(_ mqtt: CocoaMQTT, didConnectAck ack: CocoaMQTTConnAck) { + + print("didConnectAck: \(ack)") + if ack == .accept { + //delegate?.onMqttConnected() + + // if let topic = mqttSettings.subscribeTopic, mqttSettings.isSubscribeEnabled { + // self.subscribe(topic: topic, qos: mqttSettings.subscribeQos) + // } + } else { + // Connection error + var errorDescription = "Unknown Error" + switch ack { + case .accept: + errorDescription = "No Error" + case .unacceptableProtocolVersion: + errorDescription = "Proto ver" + case .identifierRejected: + errorDescription = "Invalid Id" + case .serverUnavailable: + errorDescription = "Invalid Server" + case .badUsernameOrPassword: + errorDescription = "Invalid Credentials" + case .notAuthorized: + errorDescription = "Authorization Error" + default: + errorDescription = "Unknown Error" + } + print(errorDescription) + //delegate?.onMqttError(message: errorDescription) + + //self.disconnect() // Stop reconnecting + //mqttSettings.isConnected = false // Disable automatic connect on start + } + + self.status = ack == .accept ? ConnectionStatus.connected : ConnectionStatus.error // Set AFTER sending onMqttError (so the delegate can detect that was an error while stablishing connection) + + + } + + func mqtt(_ mqtt: CocoaMQTT, didPublishMessage message: CocoaMQTTMessage, id: UInt16) { + print("didPublishMessage") + } + + func mqtt(_ mqtt: CocoaMQTT, didPublishAck id: UInt16) { + print("didPublishAck") + } + + func mqtt(_ mqtt: CocoaMQTT, didReceiveMessage message: CocoaMQTTMessage, id: UInt16) { + if let string = message.string { + print("didReceiveMessage: \(string) from topic: \(message.topic)") + } else { + print("didReceiveMessage but message is not defined") + } + } + + func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopics success: NSDictionary, failed: [String]) { + print("didSubscribeTopics: \(success.allKeys.count) topics. failed: \(failed.count) topics") + } + + func mqtt(_ mqtt: CocoaMQTT, didUnsubscribeTopics topics: [String]) { + print("didUnsubscribeTopics: \(topics.joined(separator: ", "))") + } + + func mqttDidPing(_ mqtt: CocoaMQTT) { + print("mqttDidPing") + } + + func mqttDidReceivePong(_ mqtt: CocoaMQTT) { + print("mqttDidReceivePong") + } + + func mqttDidDisconnect(_ mqtt: CocoaMQTT, withError err: Error?) { + print("mqttDidDisconnect") + } +}