AndroidアプリケーションでMQTT受信

今回はAndroidアプリケーションでMQTTを受信するプログラムに挑戦します。UI開発は最新ツール「Jetpack Compose」を使用しています。 MQTTブローカーはMosquittoを利用しました。こちらを参照してください。【https://mosquitto.org/】
あれこれ調べながら作成しましたが、理解が追いつかず、いろいろおかしい箇所もあるかと思います。 もし流用される場合は、適宜修正してください。ご利用は自己責任でお願いいたします。

※この記事は2023/07/16時点の情報です。

MQTTの基本的な概念と仕組み
MQTT(Message Queue Telemetry Transport)は、軽量なメッセージングプロトコルで、特に低帯域幅・低電力環境での通信に適しています。 IoT(Internet of Things)デバイスやネットワークアプリケーションの間でデータを効率的にやり取りするために使用されます。 以下にMQTTの基本的な概念と仕組みを分かりやすく解説します。

1.ブローカー(Broker)
MQTTはクライアント間で直接通信せず、メッセージを中継するサーバーを使います。このサーバーをMQTTブローカー(Broker)と呼びます。 ブローカーはクライアントから送信されたメッセージを受け取り、宛先のクライアント送信します。

2.クライアント(Client)
MQTTを使用するデバイスやアプリケーションがクライアントです。 クライアントはブローカーに接続してメッセージを送受信します。1つのブローカーに複数のクライアントが接続できます。

3.トピック(Topic)
メッセージの配信先を識別するためにトピックが使用されます。 トピックは階層的な構造を持っており、「/」で区切られた階層名のような形で表現されます。
(例)test/myTopic

4.パブリッシュ(Publish)
クライアントがメッセージを送信することをパブリッシュと言います。 クライアントは対象のトピックに対してメッセージをパブリッシュすることで、 そのトピックに興味を持っている他のクライアントにメッセージを送信します。

5.サブスクライブ(Subscribe)
クライアントが対象のトピックに対してメッセージを受信することをサブスクライブと言います。 クライアントは興味のあるトピックに対してサブスクライブし、ブローカーはそのトピックに関連するメッセージを送信します。

6.QoS(Quality of Service)
メッセージの配信品質をQoSレベルとして指定できます。QoSレベルには3つあります。
■QoS 0:メッセージが1回だけ配信されることを保証しますが、メッセージの到着が確認されません。
■QoS 1:メッセージが少なくとも1回は到着することが保証されますが、重複が発生する場合があります。
■QoS 2:メッセージが正確に1回だけ到着することが保証されますが、処理に時間がかかることがあります。

【build.gradle(:app)】
MQTTを使用するための次のライブラリを追加しています。
implementation "org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5"

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    namespace 'com.sample.samplez'
    compileSdk 33

    defaultConfig {
        applicationId "com.sample.samplez"
        minSdk 30
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.3.2'
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {
    implementation 'androidx.core:core-ktx:1.8.0'
    implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.5.1'
    implementation platform('androidx.compose:compose-bom:2022.10.00')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
    debugImplementation 'androidx.compose.ui:ui-tooling'
    debugImplementation 'androidx.compose.ui:ui-test-manifest'
    // MQTTライブラリ
    implementation "org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5"
}

【MainActivity.kt】
今回のメインとなるクラスです。MQTT受信したメッセージを画面に表示します。

package com.sample.samplez

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.*
import androidx.compose.ui.tooling.preview.Preview
import org.eclipse.paho.client.mqttv3.*
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence
import java.nio.charset.Charset

class MainActivity : ComponentActivity() {
    //エミュレータでlocalhostを指定する場合は「10.0.2.2」を使うべし
    private val mqttServerUri = "tcp://10.0.2.2:1883"
    private val mqttTopic = "test/Topic1"
    private lateinit var mqttClient: MqttClient
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val messageState = remember { mutableStateOf("") }
            MqttSubscriptionApp(messageState)
            // MQTTサブスクライブ
            startMqttSubscription(messageState)
        }
    }

    private fun startMqttSubscription(messageState: MutableState) {
        try {
            mqttClient = MqttClient(mqttServerUri, MqttClient.generateClientId(), MemoryPersistence())
            val options = MqttConnectOptions()
            options.isAutomaticReconnect = true
            options.isCleanSession = false
            mqttClient.connect(options)

            mqttClient.setCallback(object : MqttCallback {
                override fun connectionLost(cause: Throwable?) {
                    // 接続が失われたときの処理
                }

                override fun messageArrived(topic: String?, message: MqttMessage?) {
                    // 新しいメッセージが到着したときの処理
                    val receivedMessage = message?.toString()
                    if (!receivedMessage.isNullOrEmpty()) {
                        // UTF-8に変換する
                        val utf8String = receivedMessage.toByteArray(Charset.forName("UTF-8"))
                        // UTF-8に変換されたバイト配列を文字列に戻す
                        val convertedString = String(utf8String, Charset.forName("UTF-8"))
                        // messageStateの値を更新する
                        messageState.value = convertedString
                    }
                }

                override fun deliveryComplete(token: IMqttDeliveryToken?) {
                    // メッセージの送信が完了したときの処理
                }
            })

            mqttClient.subscribe(mqttTopic, 0)
        } catch (e: MqttException) {
            e.printStackTrace()
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MqttSubscriptionApp(messageState: MutableState) {
    Column {
        TopAppBar(title = { Text(text = "MQTTで受信してみよう") })
        Surface {
            Column {
                Text(text = "受信したメッセージ:")
                Text(text = messageState.value)
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MaterialTheme {
        val messageState = remember { mutableStateOf("") }
        MqttSubscriptionApp(messageState)
    }
}

動作確認
下記の手順で動作確認を行います。受信が成功していれば画面に受信したメッセージが表示されます。

手順1
まずはAndroid Studioでエミュレータを起動し、作成したアプリケーションを実行します。

MQTT受信するアプリケーションの起動

手順2
今回はWindows版のMosquittoをc:\Program Filesにインストールしてあります。 コマンドプロンプトで下記コマンドを実行し、MQTTブローカーを起動します。
"c:\Program Files\mosquitto\mosquitto.exe" -v

手順3
手順2とは別のコマンドプロンプトを起動し、下記コマンドを実行して"SEND MESSAGE"というメッセージを送信してみます。
"c:\Program Files\mosquitto\mosquitto_pub.exe" -h localhost -t "test/Topic1" -m "SEND MESSAGE"

手順4
アプリケーションの画面に受信したメッセージが表示されたことを確認します。

MQTT受信したメッセージの確認

今回はKotlinでMQTTのメッセージを受信するサンプルプログラムを紹介しました。 どんな感じか掴むことができましたか?

KotlinでMQTT処理を覚えられましたか?

よく分からないという人は、全部まとめてではなく、少しずつ自分でいろいろ試してみると良いかも知れません。 頑張っていきましょう!

管理人情報