Android

[Android] MQTT Client 구현 + Mosquitto Server on Window

욘두로이드 2024. 4. 6. 21:04

이전 포스팅에서 다룬 이론을 토대로 MQTT서비스를 직접 구현해 보도록 하겠습니다.

2024.03.29 - [Protocol] - MQTT 기초 이론 (이 글 하나만 보면 됨)

 

MQTT 기초 이론 (이 글 하나만 보면 됨)

MQTTT란? MQTT는 머신 대 머신 통신에 사용되는 게시/구독 기반 메시징 프로토콜이다. 스마트 센서, 웨어러블 및 기타 사물 인터넷 디바이스는 리소스 제약이 있는 네트워크를 통해 제한된 대역폭

dev-yangkj.tistory.com

 

하려고 하는 것!

1. Windows PC에서 MQTT Broker(mosquitto 사용)를 돌린다.

2. Android App으로 Client를 구현한다.

3. 서로 메시지를 주고받는다


 

1. Broker 구축

서버를 구현해 놓은 웹사이트도 있으니 사용해도 되지만 검은 바탕 흰 글자의 아름다움을 위해 

MQTT 서버 역할을 할 Mosquitto라는 프로그램을 pc에 설치합니다.

아마 다양한 환경을 지원하고 가벼워서 많이 쓰는 듯합니다.

 

https://mosquitto.org/download/

 

Download

Source mosquitto-2.0.18.tar.gz (GPG signature) Git source code repository (github.com) Older downloads are available at https://mosquitto.org/files/ Binary Installation The binary packages listed be

mosquitto.org

 

위 사이트로 가서 본인의 운영체제에 맞게 설치를 해줍니다.

 

mosquitto-2.0.18-install-windows-x64.exe 실행

 

 

Visual Studio 랑 같이 쓸 수 있나 본데 저는 패스하겠습니다.

 

경로는 Default로 진행

 

환경변수 설정

 

관리자 권한으로 CMD 창 열어서 설치 확인

 

mosquitto -v

 

이 명령어는 모든 통신 로그를 보며 실행하는 명령어입니다.

 

1883 포트로 열렸고 2.0.18 버전으로 확인됩니다.

 

이제 이 창은 그대로 두고 cmd 창을 하나 더 열어서 테스트해 보겠습니다.

 

 

net start mosquitto

 

명령어로 서비스를 시작해 줍니다.

 

그런 다음 testTopic이라는 이름의 토픽을 구독

mosquitto_sub -h localhost -t /testTopic

 

-h 뒤에는 호스트의 주소가 들어갑니다.

-p 옵션으로 포트지정이 가능한데 일단 기본포트 사용

 

 

처음 열어놓았던 창에서 연결과 구독 로그가 보입니다.

밑에 PINGREQ, PINGESP 가 보이는데 연결 체크를 위해 보내는 메시지입니다.

Mosquitto에 디폴트로 들어가 있나 봅니다.

 

이제 창 하나 더 열어서 Publish도 해보도록 하겠습니다.

mosquitto_pub -h localhost -t /testTopic -m "Hello MQTT"

 

수신확인

 

여기까지 하면 MQTT Client와 Server를 만들고 메시지도 주고받아본 것입니다.

 


2. AOS Client 구현

이제 안드로이드에서 클라이언트를 구현해서 테스트해 보겠습니다.

빠르고 간편한 개발을 위해 라이브러리를 사용하겠습니다.

 

https://github.com/hannesa2/paho.mqtt.android

 

GitHub - hannesa2/paho.mqtt.android: Kotlin MQTT client for Android

Kotlin MQTT client for Android . Contribute to hannesa2/paho.mqtt.android development by creating an account on GitHub.

github.com

 

 

settings.gradle.kts 파일에 메이븐 저장소 jitpack URL 추가

pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

rootProject.name = "Mqttt Aos Test"
include(":app")

 

 

Module 단위 gradle에 dependency 추가

dependencies {

    ...

	// MQTT
    implementation("androidx.legacy:legacy-support-v4:1.0.0")
    implementation("com.github.hannesa2:paho.mqtt.android:4.2.3")

}

 

 

간단하게 MainActivity onCreate에 연결하는 코드부터 작성해 보고 테스트해 보겠습니다.

package com.example.mqtttaostest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import info.mqtt.android.service.MqttAndroidClient
import org.eclipse.paho.client.mqttv3.IMqttActionListener
import org.eclipse.paho.client.mqttv3.IMqttToken
import org.eclipse.paho.client.mqttv3.MqttClient
import org.eclipse.paho.client.mqttv3.MqttConnectOptions

class MainActivity : AppCompatActivity() {

    private val scheme = "tcp"
    //에뮬레이터로 테스트 할 때 로컬 호스트 주소
    private val host = "10.0.2.2"
    private val port = "1883" //기본포트
    private val serverUri = "$scheme://$host:$port"
    private lateinit var client: MqttAndroidClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        client = MqttAndroidClient(
            context = this,
            clientId = MqttClient.generateClientId(),
            serverURI = serverUri
        )
        connect()

    }

    private fun connect() {
        val option = MqttConnectOptions()

        client.connect(
            options = option,
            userContext = null,
            callback = object : IMqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken?) {
                    Log.d("MQTT", "연결성공")
                }

                override fun onFailure(
                    asyncActionToken: IMqttToken?,
                    exception: Throwable?
                ) {
                    Log.d("MQTT", exception?.cause?.message?:"연결실패")
                }

            }
        )
    }

}

 

실행결과

MQTT                    com.example.mqtttaostest             D  연결성공

 

 

이제 연결 성공이면 토픽을 구독하고 메시지도 보내보겠습니다.

연결성공 콜백 함수 안에 콜백 등록하고 구독하는 함수 추가

client.connect(
    options = option,
    userContext = null,
    callback = object : IMqttActionListener {
        override fun onSuccess(asyncActionToken: IMqttToken?) {
            Log.d("MQTT", "연결성공")
            setCallback()
            subscribeTestTopic()
        }

        override fun onFailure(
            asyncActionToken: IMqttToken?,
            exception: Throwable?
        ) {
            Log.d("MQTT", exception?.cause?.message?:"연결실패")
        }

    }
)
private fun setCallback() {
        client.setCallback(
            object : MqttCallback {
                override fun connectionLost(cause: Throwable?) {
                    Log.d("MQTT", "연결 끊김")
                }

                override fun messageArrived(topic: String?, message: MqttMessage?) {
                    Log.d("MQTT", "메시지도착! topic:$topic, mesaage:$message")
                }

                override fun deliveryComplete(token: IMqttDeliveryToken?) {
                    Log.d("MQTT", "메시지 전송 성공")
                }

            }
        )
    }
private fun subscribeTestTopic() {
        client.subscribe("testTopic", 1, null, object: IMqttActionListener {
            override fun onSuccess(asyncActionToken: IMqttToken?) {
                Log.d("MQTT", "구독 성공")
            }

            override fun onFailure(
                asyncActionToken: IMqttToken?,
                exception: Throwable?
            ) {
                Log.d("MQTT", "구독 실패")
            }

        })
    }

 

 

실행결과

MQTT                    com.example.mqtttaostest             D  연결성공
MQTT                    com.example.mqtttaostest             D  구독 성공

 


3. 메시지 주고받기

 

정말로 구독 잘 되었는지 cmd 창에서 메시지를 보내보겠습니다.

Logcat에 로그가 잘 출력됩니다.

 

이제 반대로 안드로이드 클라이언트에서 브로커로 메시지 전송

버튼하나 만들어서 테스트해 보겠습니다.

 

Publish 함수 정의

fun publish(
        topic: String,
        msg: String, qos: Int = 1,
        retained: Boolean = false,
    ) {
        try {
            val message = MqttMessage()
            message.payload = msg.toByteArray()
            message.qos = qos
            message.isRetained = retained
            client.publish(topic, message, null, object : IMqttActionListener {
                override fun onSuccess(asyncActionToken: IMqttToken?) {
                    Log.d("MQTT", "publish 성공")
                }

                override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
                    Log.d("MQTT", "publish 실패")
                }
            })
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }

 

 

버튼에 온클릭 등록

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        client = MqttAndroidClient(
            context = this,
            clientId = MqttClient.generateClientId(),
            serverURI = serverUri
        )
        connect()
        findViewById<Button>(R.id.publishButton).setOnClickListener {
            publish(
                topic = "testTopic",
                msg = "This message is from aos"
            )
        }
    }

 

실행결과

 

구독한 클라이언트(cmd창)에 메시지가 출력되었고, 보낸 쪽(AOS)에서도 메시지 전송 성공 로그가 잘 보입니다.

 

 

이 예제는 정말 기본적인 기능만 사용해 보았는데 Mosquitto는 보안설정이나 그 밖에 다양한 기능들이 많습니다.

저도 이제부터 하나하나 사용해 볼 예정이고 계속 포스팅해보겠습니다.

 

긴 글 읽어 주셔서 감사합니다.

 

끝!