swiftxcodeswiftuicocoapodsios-mqtt-client-framework

CocoaMQTT init, Can't seem to connect to MQTT Broker


Please excuse how much of a newbie I am. I started on swift at the beginning of last week...

I am trying to make an app that uses BLE and or MQTT to talk to a raspberry pi through a broker. the BLE side is ok but the MQTT (for when out of BLE range) I'm having trouble with. This is in a swift file of a couple of classes:

import Foundation
import CocoaMQTT

class MQTTManager{

    static let shared = MQTTManager()

    private var mqttClient: CocoaMQTT
   
    init() {
        let clientID = "swift-Trial-13579"
        let host = "IP.Goes.Here"
        let port = UInt16(1883)
        self.mqttClient = CocoaMQTT(clientID: clientID, host: host, port: port)
        self.mqttClient.username = "User"
        self.mqttClient.password = "Pass"
        self.mqttClient.keepAlive = 60
        self.mqttClient.connect()
        sendMessage(topic: "app/init", message: "init called")
        print("MQTT Init Called")
    }

    func sendMessage(topic:String, message:String){
        self.mqttClient.publish(topic, withString: message)
        print("publish MQTT called with message: \(message) and a topic of: \(topic)")
    }

}

class useProperties: ObservableObject{
    @Published var useMQTT = false
    @Published var recallMQTTScene = false
    @Published var MQTTScene = 0
    
    init(){
    }
}

I then have 3 views, ContentView

import SwiftUI
import CocoaMQTT

struct ContentView: View {
    
        
    @State public var Connection:Bool = false
    @State public var SceneMessqe: String = ""
    let MQTTHandle = MQTTManager()
    
// the main view actually is here
    var body: some View{
       // SceneButton(function: { self.MQTTPub})
        VStack{
            HStack {
                Text("MultiControl POC")
                    .font(.title)
                //.padding()

            }
            Spacer()
            mainSwitch()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(BLEManager())
    }
}

mainSwitch

import SwiftUI
import CocoaMQTT

struct mainSwitch: View {
    
    @State public var Connection:Bool = false
    @State private var selection: String? = nil
    @StateObject var bleManager = BLEManager() // gets from environment.
    public var properties = useProperties()
    @State private var selectDeviceShown = false
    @State var isPresenting = false
    public var MQTTHandle = MQTTManager()
    
    var body: some View {
        VStack (spacing: 0){
            NavigationView{
            HStack {
                Spacer()
                NavigationLink(destination: sheetView(), isActive: $isPresenting) { EmptyView() }// added for nav but not working
                Menu("Menu") {
                    Button("BLE Setup", action: {
                        self.isPresenting = true // added to trigger nav not workinh
                        print("Setup button pressed")
                        //selectDeviceShown = true
                    })
                    Button("Reconnect", action: {
                        bleManager.myCentral.connect(bleManager.wrappedControllers[bleManager.currentSceneSwitchControllerUUID]!.wrappedPerh)
                    })
                    Button(action: {
                        MQTTManager.init()
                        Connection.toggle()
                        properties.useMQTT = true
                        print("connect/disconnect pressed useMQTT = \(properties.useMQTT)")
                        }, label: {
                            Text(Connection ? "MQTT Disconnect":"MQTT Connect")
                        })
                    Button("Cancel", action: {
                        print("oops")
                    })
                }
                .foregroundColor(Connection ? .green : .red)
                .padding(38)
                .overlay(
                    RoundedRectangle(cornerRadius: 15)
                        .stroke(lineWidth: 2)
                        .foregroundColor(Connection ? .green : .red)
            )
                Spacer()
                Spacer()
            }
            .fixedSize(horizontal: false, vertical: true)
            .frame(maxHeight: 10)
            .padding()
            }
            HStack{
                Spacer()
                VStack{
                    Spacer()
                    SceneButton(sceneName: "Scene 1", sceneNumber: 1)
                    Spacer()
                    SceneButton(sceneName: "Scene 3" , sceneNumber: 3)
                    Spacer()
                    SceneButton(sceneName: "Scene 5", sceneNumber: 5)
                    Spacer()
                }
                Spacer()
                VStack{
                    Spacer()
                    SceneButton(sceneName: "Scene 2", sceneNumber: 2)
                    Spacer()
                    SceneButton(sceneName: "Scene 4", sceneNumber: 4)
                    Spacer()
                    SceneButton(sceneName: "Off", sceneNumber: 6)
                    Spacer()
                }
                Spacer()
            }
            Spacer()
        }
        .environmentObject(useProperties())
        .environmentObject(BLEManager())
    }
}

struct mainSwitch_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            mainSwitch()
                .environmentObject(BLEManager())
                .environmentObject(useProperties())
        }
    }
}

and finally sceneButton

import SwiftUI
import CocoaMQTT

struct SceneButton: View {
    
    var sceneName: String
    var sceneNumber: Int
    let properties = useProperties()
    @State private var isDisabled: Bool = true
    @State private var isDuringGesture: Bool = false
    @StateObject private var bleManager = BLEManager()
    let btnClr:Color = Color.orange
    let btnClrOutr:Color = Color.red
    let btnPressedClr:Color = Color.gray
    let MQTTHandle = MQTTManager()
    
    var body: some View {

        if (properties.useMQTT){
            Button(sceneName) {
                bleManager.writeToCharacteristicButtonPress(peripheral: bleManager.currentSceneSwitchControllerUUID, sceneToGoToo: (sceneNumber).description, setButtonPressed: true) // Note the number is the same number as the button.
                bleManager.writeToCharacteristicButtonPress(peripheral: bleManager.currentSceneSwitchControllerUUID, sceneToGoToo: (sceneNumber).description, setButtonPressed: false)
                print("BLE Button" + sceneName)
                print("\(properties.useMQTT)")
            }
            .foregroundColor(.white)
                .frame(minWidth: 100)
                .padding()
                .background(Color(red: 1, green: 0.1, blue: 0.1))
                .cornerRadius(10)
                .padding(10)
                .font(.title2)
                .font(.system(size: 20))
        }else{
            Button(sceneName) {
                print("\(properties.useMQTT)")
                MQTTHandle.sendMessage(topic: "apptest/scene", message: "\(sceneNumber)")
            }
                .frame(minWidth: 100)
                .padding()
                .background(Color(red: 0.40, green: 0.60, blue: 0))
                .cornerRadius(10)
                .padding(10)
        }
    }
}

struct SceneButton_Previews: PreviewProvider {
    static var previews: some View {
        SceneButton(sceneName: "Scene X", sceneNumber: 42) //, publishSceneMQTT: sceneHandle
            .environmentObject(BLEManager())
            .environmentObject(useProperties())
    }
}

Currently when the app loads it calls the MQTT init many times, and then doesn't connect reliably, occasionally, maybe 1 in 10 times it connects to send a single message then I can't send more. Ideally it would only connect when I press the connect button in the menu in mainSwitch. However each button (SceneButton) should publish something slightly different.

Firstly, is it an issue that it keeps calling init at start?

Secondly, is there something visible that I am doing wrong to mean its not reliably connecting?

thirdly, (least important) in sceneButton the button should change whether using BLE or MQTT, this variable, useMQTT is set in the mainSwitch file. but doesn't change in sceneButton, what have I done wrong?


Solution

  • You use everywhere (!) different instances of MQTTManager, because create it via init, instead you should use everywhere MQTTManager.shared, like

    struct ContentView: View {
        
            
        @State public var Connection:Bool = false
        @State public var SceneMessqe: String = ""
    //    let MQTTHandle = MQTTManager()       // << not this !!
        let MQTTHandle = MQTTManager.shared    // << this one !!
    

    so review all your code and fix as above.