Difference between revisions of "MQTT: Android Client Simple"

From OnnoWiki
Jump to navigation Jump to search
(Created page with "Android and MQTT: A Simple Guide How to develop an MQTT Client with Android Simplicity is the keynote of all true elegance MQTT stands for Message Queuing Telemetry Transport...")
 
Line 1: Line 1:
Android and MQTT: A Simple Guide
+
MQTT is one of the popular data communication or messaging protocols that are becoming widely used for machine-to-machine (M2M) communication, or the computer network trend that is popularly coined as “Internet of Things”. MQTT (Message Queue Transport Telemetry) is a messaging protocol with a publish-subscribe pattern, one of the messaging protocol regarded as “light-weight”, which is important for the Internet of Things architecture, because it heavily involves low-powered devices, such as sensor nodes, microcontrollers, and the like.
How to develop an MQTT Client with Android
 
Simplicity is the keynote of all true elegance
 
MQTT stands for Message Queuing Telemetry Transport and it is a powerful messaging transport protocol mostly used in Machine to Machine (M2M) and Internet of Things (IoT) communication contexts.
 
MQTT is preferred in these contexts, because it’s easy to implement and it’s a perfect fit for devices with limited resources.
 
  
But, why not taking advantage of these pros and using it also in smartphones?
+
One of the many uses of the MQTT protocol is to send sensor data from embedded devices. Sometimes we want those data to be sent to our smartphones which could help us monitor some important things from afar, and that’s what I’ll be showing you here in this tutorial, specifically using the Android OS.
In this article, we’ll develop an Android mobile application that uses MQTT protocol.
 
  
Why MQTT for your project?
+
If you’re too busy or too lazy to be reading this tutorial, you can get the full code in my Github page.
The rival of MQTT is the old, but gold, HTTP protocol.
 
But, why should you choose MQTT protocol for your project instead?
 
Below, a brief comparison table to better clarify the main aspects:
 
  
To sum up, MQTT is simpler, smaller and more secure compared to HTTP.
+
Software used in this tutorial, be sure to have them installed:
In the next section, we’ll analyze in depth the key concepts listed in the above table.
+
-Android Studio
MQTT in a nutshell
+
-Python 2.7
MQTT is a publish-subscribe client-server messaging transport protocol.
 
Let’s be clear about what we mean for publish-subscribe and client-server architectures.
 
Publish-Subscribe
 
In publish/subscribe architecture, senders (publishers) don’t transmit messages directly to specific receivers (subscribers), but instead classify published messages into “categories” (called topics) without knowledge of which subscribers there may be.
 
  
Publishers and subscribers never contact each other directly. The connection between them is handled by a third component called broker.
+
Outline what I will be covering:
A practical example of publish/subscribe pattern in real life could be the newspaper: journalists (publishers) write several articles in the newspaper (broker) but they don’t know how many and which readers (subscribers) will read that article.
+
1. Sorting out MQTT dependencies in Android Studio
Client-Server
+
2. Setting up a cloud MQTT broker in CloudMQTT
In Client-Server architecture, a client connects to a server for the use of a service.
+
3. Setting up a basic MQTT client and service in Android
In MQTT context, an MQTT client is a device that connects to an MQTT broker over a network. The service provided by the MQTT broker (server) is the possibility to publish and/or subscribe on one or many topics.
+
4. Setting up a mock data publisher with Python
In MQTT, a client can be a publisher and subscriber or both.
+
5. Visualizing data with MPAndroidCharts library
  
Let’s start to communicate: Connection
 
Before starting the message exchange over topics, the client needs to initiate the communication by sending the CONNECT message to the broker.
 
With this message, a client presents itself to a broker providing the following main information:
 
ClientID
 
ClientID is an unique ID used by brokers to identify the client and store information (called session) about it.
 
An empty ClientID means an “anonymous” connection: therefore, the broker does not memorize any information about the client.
 
CleanSession
 
If the CleanSession is set to false and the broker has information stored for that client, broker uses the existing session and delivers previously queued messages to the client.
 
Instead, if the flag is set to true, it means discarding all existing sessions and messages for that client (mandatory if the ClientId is empty).
 
KeepAlive
 
This interval, express in seconds, defines the maximum period of time that broker and client can remain in contact without sending a message. The client needs to send regular PING messages, within the KeepAlive period, to the broker to maintain the connection alive.
 
Username and Password (optional)
 
Client can send a username and password to improve communication security.
 
WillMessage (optional)
 
A client can specify its last will message in the form of a MQTT message and topic. Broker will send this message, on behalf of the client, when the client “badly” disconnects.
 
Topic
 
As mentioned before, MQTT broker uses topic to decide which subscriber receives which message.
 
A client doesn’t create a topic before using it. A server accepts each valid topic without any initialization.
 
Topic is a string with the following characteristics:
 
At least 1 character in length
 
Case-sensitive
 
Composed by one or more levels separated by “/”
 
Let’s make an example!
 
Suppose you have temperature sensors scattered around your house. These sensors communicate with the thermostat using MQTT and the thermostat uses this information to regulate the home heating system.
 
  
Each sensor (publisher) communicates its temperature by publishing on a specific MQTT topic and thermostat (subscriber) monitors temperatures by checking these topics; below an example of the topics structure:
+
You may skip some steps if you think you know what you’re doing. I’ve structured this tutorial as modular as possible so you can replace any of the steps with your own way.
myhome/groundfloor/kitchen
 
myhome/groundfloor/dining
 
myhome/groundfloor/livingroom
 
myhome/groundfloor/bedroom1
 
myhome/groundfloor/bedroom2
 
myhome/groundfloor/bathroom
 
Publish
 
A client writes data on a topic using the PUBLISH message.
 
This message contains the following information:
 
Topic Name
 
Payload: content of the message. MQTT protocol is data-agnostic. Payload is simply an alphanumeric string that needs to be interpreted by clients.
 
QoS level: indicates the Quality of Service Level (0, 1, 2). We’ll discuss it later.
 
Retain flag: defines if the message has to be saved by the broker as the last known good value for the topic. When a new client will subscribe to this topic, it receives the last retained message.
 
Below an example of the kitchen sensor publishing its reading temperature on kitchen topic:
 
Topic name: myhome/groundfloor/kitchen
 
Payload: 21.5
 
QoS level: 1
 
Retain flag: false
 
The temperature, inside the payload, is expressed in celsius degree (°C) with decimal point number. The subscriber (thermostat) needs to know this representation in order to correctly interpreter the temperature data.
 
Subscribe
 
To receive messages on topics, client sends a SUBSCRIBE message to the broker.
 
This message contains a list of subscriptions composed by:
 
Topic name
 
QoS level
 
Below an example of the thermostat subscribing on all sensor topics.
 
Topic name: myhome/groundfloor/kitchen
 
QoS level: 1
 
Topic name: myhome/groundfloor/dining
 
QoS level: 1
 
...
 
Topic name: myhome/groundfloor/bathroom
 
QoS level: 1
 
Unsubscribe
 
To delete existing subscriptions to a topic, client sends a UNSUBSCRIBE message to the broker.
 
Content is the same as the SUBSCRIBE message: a list of subscriptions.
 
QoS: Quality Of Service
 
QoS level is an agreement between sender and receiver on the guarantee of delivering a message.
 
But why is it so important? It improves reliability.
 
MQTT protocol manages the message retransmission and guarantees delivery, making communication in unreliable networks a bit less complex.
 
There are 3 levels:
 
0 — At most once
 
No guarantee of delivery. This level is called “fire and forget”.
 
Use it when you have a stable communication channel and when the loss of messages is acceptable.
 
1 — At least once
 
Guarantees that a message is delivered at least one time to the receiver.
 
Use it when clients can tolerate duplicate messages. It’s the most used.
 
2 — Exactly once
 
Guarantees that message is received only once by the receiver.
 
Use it when your application is critical and you cannot tolerate loss and duplicate messages.
 
By raising the QoS level, you will increase the reliability of the communication, but you will decrease the performance.
 
Android: Let’s get your hands dirty
 
Dealing with MQTT protocol on Android is quite easy.
 
MQTT works on TCP/IP stack, this means that the only requirement of the mobile device is the capability to connect to the internet.
 
  
There are only few Android libraries which implements MQTT protocol, but fortunately they are well documented and work fine.
+
1. Dependecies
The most famous libraries are:
 
Eclipse Paho Android Service
 
HiveMQ MQTT Client library
 
I have choosen the Eclipse Paho library, because it’s the most used one and it’s implemented in different programming languages and platforms.
 
  
Here, instructions on how to import Paho library in Android Studio.
+
First we need to sort out dependencies which are libraries needed to setup an MQTT client and service in an Android app. We will be using the Paho MQTT Client and Android Service provided by Eclipse.
Let’s coding!
 
The sample/simple MQTT Android app
 
To better understand how to use the MQTT Paho Library, I have developed a simple Android mobile application in Kotlin language.
 
App is composed by two fragments:
 
Connect fragment
 
It allows you to insert the MQTT Broker details you want to connect to.
 
  
Client fragment
+
According to official Paho Eclipse Github, we can install the library to our app using Maven, Gradle, or from source, but in this tutorial I will be using Gradle. So to sort out the dependency, simply add these lines to our build.gradle of our Android Studio project.
In this page, you can interact with the MQTT Broker by publishing and subscribing on the topics.
 
  
Let’s start: Connect to MQTT Broker
+
repositories {
First of all, you need a MQTT Broker.
+
    maven {
There are several websites offering free and/or enterprise MQTT broker service. Below, a list:
+
        url "https://repo.eclipse.org/content/repositories/paho-snapshots/"
HiveMQ
+
    }
Mosquitto
+
}
CloudMqtt
+
And in our build.gradle of our Android app.
Adafruit IO
 
For demonstration purposes, I will use the free HiveMQ Public MQTT Broker. But, in general, I suggest Adafruit IO, because it provides several cool dashboards and more other functionalities.
 
Let’s take a look in depth at the Connect fragment.
 
This page allows inserting MQTT Broker connection details.
 
In the example below, we fill in the Server URI, but we left other fields empty because we’re using a public server that doesn’t need this information.
 
  
But, what happens behind the UI?
+
dependencies {
By opening this page, you do instantiate the MQTT Android client.
+
    compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
MQTTClient is a wrapper of the Paho Library that implements the main functionalities of MQTT transport protocol.
+
    compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
Below, class declaration and its main functions.
+
}
 +
A notification will come up about our Gradle configuration being changed, if so click Sync Now. Oh, and please to have your internet on in this process or the Gradle sync will fail.
  
By pressing the “CONNECT” button, you start the MQTT Broker connection procedure:
+
Well that’s it, eventually Gradle will converge our Android app with the Paho MQTT Client and Android Service libraries.
  
Let’s talk with the broker: Publish
+
2. Setting up cloud MQTT broker with CloudMQTT
If MQTT broker connection succeeds, you will be redirected to the Client fragment.
 
  
By filling in the “Topic” and “Message” fields and pressing the “PUBLISH” button, you send the desired message on the selected topic.
+
There are a couple of cloud MQTT brokers that are available in the internet right now, like CloudMQTT and HiveMQ, but in this tutorial, I will teach how to set up a free private cloud MQTT broker using CloudMQTT. You can sign up for a free account at https://customer.cloudmqtt.com/login, just follow the instructions to sign up.
Below, the code snippet of publish() function:
 
  
Broker, tell me something: Subscribe and unsubscribe
+
cloudmqtt
If you want to receive messages from the broker, you have to subscribe to a topic.
 
To do that, fill the “Topic” field with the topic name and press the “SUBSCRIBE” button.
 
  
Here’s what happens when you click this button:
+
Next, you will be redirected to the CloudMQTT instances page. Click the + Create button to create a new CloudMQTT instance. Now you would be in the Create new CloudMQTT instance page. Insert an instance name, choose any Data center available, and make sure for the “Plan” choose the “Cute Cat”, because we like free stuff.
  
When a message is published in the subscribed topic, you will be notified with a “Toast” message. Below, an example:
+
instance
  
To stop receiving messages from a specific topic, fill in the “Topic” field with the topic name and press the “UNSUBSCRIBE” button.
+
In the next page, you can see your new created instance, click the details button of that instance. This is what you will see, (never mind the red scribbles):
Below, the unsubscribe() function:
 
  
Time to say bye-bye: Disconnection
+
account
To properly terminate communication with the broker, you must send the disconnect message by pressing the “DISCONNECT” button.
 
Below, the disconnect() function:
 
  
Conclusion
+
Done! We have our own private cloud MQTT broker. Later we will use those credentials to connect our MQTT Android client to the broker.
And, that’s all!
 
Have you seen how easy it is to work with MQTT?
 
Next time, think twice before using HTTP/REST for server communication.
 
If the data you need to send is not too complex and you want a lightweight and easy protocol to implement, use MQTT.
 
Here, you can find the project of the Android Mobile Application described in this article!
 
  
References
+
3. Setting up MQTT Client and Service
If you want to learn more about MQTT, I recommend the HiveMQ MQTT Essentials guide.
+
 
 +
Before we code anything, we need to set up our AndroidManifest.xml file to let our app have permissions to access the Internet, access the network state, and let our app to stay alive as a service. Add these lines before the opening <application tag in our AndroidManifest.xml.
 +
 
 +
<uses-permission android:name="android.permission.INTERNET" />
 +
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 +
<uses-permission android:name="android.permission.WAKE_LOCK"/>
 +
And register our MQTT Android Service in our app before the closing application tag using this piece of code:
 +
<service android:name="org.eclipse.paho.android.service.MqttService" />
 +
So it should look like this: manifest Ok great, now lets begin to code shall we.
 +
 
 +
So first, lets create a new package called helpers in our app, which will contain our MQTT helper class. Then create a new Java Class inside that package called MQTTHelper.
 +
 
 +
Add the following code inside our MqttHelper class, please adjust the credentials (serverUri, username, password) accordingly with your CloudMQTT instance from the previous step. Please note: the format of the server URI is tcp://server:port
 +
 
 +
public class MqttHelper {
 +
    public MqttAndroidClient mqttAndroidClient;
 +
 
 +
    final String serverUri = "tcp://m12.cloudmqtt.com:11111";
 +
 
 +
    final String clientId = "ExampleAndroidClient";
 +
    final String subscriptionTopic = "sensor/+";
 +
 
 +
    final String username = "xxxxxxx";
 +
    final String password = "yyyyyyyyyy";
 +
 
 +
    public MqttHelper(Context context){
 +
        mqttAndroidClient = new MqttAndroidClient(context, serverUri, clientId);
 +
        mqttAndroidClient.setCallback(new MqttCallbackExtended() {
 +
            @Override
 +
            public void connectComplete(boolean b, String s) {
 +
                Log.w("mqtt", s);
 +
            }
 +
 
 +
            @Override
 +
            public void connectionLost(Throwable throwable) {
 +
 
 +
            }
 +
 
 +
            @Override
 +
            public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
 +
                Log.w("Mqtt", mqttMessage.toString());
 +
            }
 +
 
 +
            @Override
 +
            public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
 +
 
 +
            }
 +
        });
 +
        connect();
 +
    }
 +
 
 +
    public void setCallback(MqttCallbackExtended callback) {
 +
        mqttAndroidClient.setCallback(callback);
 +
    }
 +
 
 +
    private void connect(){
 +
        MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
 +
        mqttConnectOptions.setAutomaticReconnect(true);
 +
        mqttConnectOptions.setCleanSession(false);
 +
        mqttConnectOptions.setUserName(username);
 +
        mqttConnectOptions.setPassword(password.toCharArray());
 +
 
 +
        try {
 +
 
 +
            mqttAndroidClient.connect(mqttConnectOptions, null, new IMqttActionListener() {
 +
                @Override
 +
                public void onSuccess(IMqttToken asyncActionToken) {
 +
 
 +
                    DisconnectedBufferOptions disconnectedBufferOptions = new DisconnectedBufferOptions();
 +
                    disconnectedBufferOptions.setBufferEnabled(true);
 +
                    disconnectedBufferOptions.setBufferSize(100);
 +
                    disconnectedBufferOptions.setPersistBuffer(false);
 +
                    disconnectedBufferOptions.setDeleteOldestMessages(false);
 +
                    mqttAndroidClient.setBufferOpts(disconnectedBufferOptions);
 +
                    subscribeToTopic();
 +
                }
 +
 
 +
                @Override
 +
                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
 +
                    Log.w("Mqtt", "Failed to connect to: " + serverUri + exception.toString());
 +
                }
 +
            });
 +
 
 +
 
 +
        } catch (MqttException ex){
 +
            ex.printStackTrace();
 +
        }
 +
    }
 +
 
 +
 
 +
    private void subscribeToTopic() {
 +
        try {
 +
            mqttAndroidClient.subscribe(subscriptionTopic, 0, null, new IMqttActionListener() {
 +
                @Override
 +
                public void onSuccess(IMqttToken asyncActionToken) {
 +
                    Log.w("Mqtt","Subscribed!");
 +
                }
 +
 
 +
                @Override
 +
                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
 +
                    Log.w("Mqtt", "Subscribed fail!");
 +
                }
 +
            });
 +
 
 +
        } catch (MqttException ex) {
 +
            System.err.println("Exception whilst subscribing");
 +
            ex.printStackTrace();
 +
        }
 +
    }
 +
}
 +
Now go to our activity_main.xml or any other activity you may have created, and you may see the Hello World text view which is added by default by Android Studio, let’s give it an ID so we can access it in our Java code, here I give it an ID dataReceived.
 +
 
 +
id
 +
 
 +
So our MainActivity java class should look like this, let Android Studio resolve the imports if needed.
 +
 
 +
public class MainActivity extends AppCompatActivity {
 +
    MqttHelper mqttHelper;
 +
 
 +
    TextView dataReceived;
 +
 
 +
    @Override
 +
 
 +
    protected void onCreate(Bundle savedInstanceState) {
 +
        super.onCreate(savedInstanceState);
 +
        setContentView(R.layout.activity_main);
 +
 
 +
        dataReceived = (TextView) findViewById(R.id.dataReceived);
 +
 
 +
        startMqtt();
 +
    }
 +
 
 +
    private void startMqtt(){
 +
        mqttHelper = new MqttHelper(getApplicationContext());
 +
        mqttHelper.setCallback(new MqttCallbackExtended() {
 +
            @Override
 +
            public void connectComplete(boolean b, String s) {
 +
 
 +
            }
 +
 
 +
            @Override
 +
            public void connectionLost(Throwable throwable) {
 +
 
 +
            }
 +
 
 +
            @Override
 +
            public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
 +
                Log.w("Debug",mqttMessage.toString());
 +
                dataReceived.setText(mqttMessage.toString());
 +
            }
 +
 
 +
            @Override
 +
            public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
 +
 
 +
            }
 +
        });
 +
    }
 +
So what we do here is set the callback functions for the MQTT client, the only callback function we need right now is the messageArrived function which is called every time the MQTT client receives a message. Whenever a message is received, it will set the text of our TextView in our activity according to the message received.
 +
 
 +
Now let’s try to send some data to our app.
 +
 
 +
Go back to your CloudMQTT instance and open the Websocket UI. In the topic input, enter the topic set in our app, in my case is “sensor/+”. If you have done your research about MQTT, you may recall that the “+” sign is a wildcard which simply means “any subtopic”. So let’s try sending data with the “sensor/temp” topic.
 +
 
 +
send
 +
 
 +
And in our app:
 +
 
 +
Screenshot_2017-05-11-05-35-49-859_com.frost.mqtttutorial
 +
 
 +
Magic! It’s like our computer and phones are telepathic right now!
 +
 
 +
Well, so far, our Android Project structure will look like this:
 +
 
 +
structure
 +
 
 +
4. Setting up a mock data publisher with python
 +
 
 +
So now since we can be sure that our Android App can communicate using the MQTT Protocol, we can use any device with any programming language to communicate our app with the MQTT Protocol, be it your own web server, your Arduino temperature monitoring device, or even your very own smart underwear! All you need is an MQTT broker, which we have covered in step 2.
 +
 
 +
Here I will show you an example of that case, by making a mock data publisher using python 2.7.
 +
 
 +
First you will need to install the paho.mqtt client library in python.
 +
 
 +
pip install paho-mqtt
 +
 
 +
And the code:
 +
 
 +
import json
 +
import paho.mqtt.client as mqtt
 +
import random
 +
import time
 +
import threading
 +
import sys
 +
 
 +
mqttc = mqtt.Client("client1", clean_session=False)
 +
mqttc.username_pw_set("#User", "#password")
 +
mqttc.connect("#Server", #port, 60)
 +
 
 +
 
 +
def pub():
 +
    mqttc.publish("sensor/temp", payload=random.normalvariate(30, 0.5), qos=0)
 +
    threading.Timer(1, pub).start()
 +
 
 +
pub()
 +
Please change the credentials accordingly with your CloudMQTT Instance (the hash tagged words). This piece of code will periodically (every 1 second) publish a random number somewhere around 30.
 +
 
 +
After running the above python code, you will see that your app will show a different number every second, which means the code works.
 +
 
 +
5. Visualizing data with MPAndroidCharts library
 +
 
 +
Usually for sensor data monitoring, just showing the raw data may look ugly, like your sister HAH (sorry bad joke), and that is why it may be a good idea to visualize our data using charts. Here I will use one of the most popular chart libraries available for Android, when I mean popular, I mean “the first search result on Google” popular. Try googling “android chart”, and you will find MPAndroidChart as the first result (not the ads, psst).
 +
 
 +
First things first, dependencies dependencies…
 +
 
 +
In our project’s build.gradle below the previous MQTT dependency, add `
 +
 
 +
maven { url "https://jitpack.io" }
 +
So now it should look like:
 +
 
 +
maven {
 +
    url "https://repo.eclipse.org/content/repositories/paho-snapshots/"
 +
}
 +
maven { url "https://jitpack.io" }
 +
and our app’s build.gradle add it below our MQTT dependency too:
 +
 
 +
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
 +
so now it should look like:
 +
 
 +
compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
 +
compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
 +
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
 +
Okay gradle, Sync Now!!
 +
 
 +
Now add a new Java Class in our helpers package named ChartHelper​, with the following code inside:
 +
 
 +
public class ChartHelper implements OnChartValueSelectedListener {
 +
 
 +
    private LineChart mChart;
 +
 
 +
    public ChartHelper(LineChart chart) {
 +
        mChart = chart;
 +
        mChart.setOnChartValueSelectedListener(this);
 +
 
 +
        // no description text
 +
        mChart.setNoDataText("You need to provide data for the chart.");
 +
 
 +
        // enable touch gestures
 +
        mChart.setTouchEnabled(true);
 +
 
 +
        // enable scaling and dragging
 +
        mChart.setDragEnabled(true);
 +
        mChart.setScaleEnabled(true);
 +
        mChart.setDrawGridBackground(false);
 +
 
 +
        // if disabled, scaling can be done on x- and y-axis separately
 +
        mChart.setPinchZoom(true);
 +
 
 +
        // set an alternative background color
 +
        mChart.setBackgroundColor(Color.WHITE);
 +
        mChart.setBorderColor(Color.rgb(67,164,34));
 +
 
 +
 
 +
        LineData data = new LineData();
 +
        data.setValueTextColor(Color.WHITE);
 +
 
 +
        // add empty data
 +
        mChart.setData(data);
 +
 
 +
        // get the legend (only possible after setting data)
 +
        Legend l = mChart.getLegend();
 +
 
 +
        // modify the legend ...
 +
        // l.setPosition(LegendPosition.LEFT_OF_CHART);
 +
        l.setForm(Legend.LegendForm.LINE);
 +
        l.setTypeface(Typeface.MONOSPACE);
 +
        l.setTextColor(Color.rgb(67, 164, 34));
 +
 
 +
        XAxis xl = mChart.getXAxis();
 +
        xl.setTypeface(Typeface.MONOSPACE);
 +
        xl.setTextColor(Color.rgb(67, 164, 34));
 +
        xl.setDrawGridLines(false);
 +
        xl.setAvoidFirstLastClipping(true);
 +
        xl.setEnabled(true);
 +
 
 +
        YAxis leftAxis = mChart.getAxisLeft();
 +
        leftAxis.setTypeface(Typeface.MONOSPACE);
 +
        leftAxis.setTextColor(Color.rgb(67, 164, 34));
 +
 
 +
        leftAxis.setDrawGridLines(true);
 +
 
 +
        YAxis rightAxis = mChart.getAxisRight();
 +
        rightAxis.setEnabled(false);
 +
 
 +
    }
 +
 
 +
    public void setChart(LineChart chart){ this.mChart = chart; }
 +
 
 +
    public void addEntry(float value) {
 +
 
 +
        LineData data = mChart.getData();
 +
 
 +
        if (data != null){
 +
 
 +
            ILineDataSet set = data.getDataSetByIndex(0);
 +
            // set.addEntry(...); // can be called as well
 +
 
 +
            if (set == null) {
 +
                set = createSet();
 +
                data.addDataSet(set);
 +
            }
 +
 
 +
            data.addEntry(new Entry(set.getEntryCount(),value),0);
 +
            Log.w("chart", set.getEntryForIndex(set.getEntryCount()-1).toString());
 +
 
 +
            data.notifyDataChanged();
 +
 
 +
            // let the chart know it's data has changed
 +
            mChart.notifyDataSetChanged();
 +
 
 +
            // limit the number of visible entries
 +
            mChart.setVisibleXRangeMaximum(10);
 +
            // mChart.setVisibleYRange(30, AxisDependency.LEFT);
 +
 
 +
            // move to the latest entry
 +
            mChart.moveViewTo(set.getEntryCount()-1, data.getYMax(), YAxis.AxisDependency.LEFT);
 +
 
 +
            // this automatically refreshes the chart (calls invalidate())
 +
            // mChart.moveViewTo(data.getXValCount()-7, 55f,
 +
            // AxisDependency.LEFT);
 +
        }
 +
    }
 +
 
 +
    private LineDataSet createSet() {
 +
        LineDataSet set = new LineDataSet(null, "Data");
 +
        set.setAxisDependency(YAxis.AxisDependency.LEFT);
 +
        set.setColor(Color.rgb(67, 164, 34));
 +
        //set.setCircleColor(Color.WHITE);
 +
        set.setLineWidth(2f);
 +
        //set.setCircleRadius(4f);
 +
        set.setFillAlpha(65);
 +
        set.setFillColor(Color.rgb(67, 164, 34));
 +
        set.setHighLightColor(Color.rgb(67, 164, 34));
 +
        set.setValueTextColor(Color.rgb(67, 164, 34));
 +
        set.setValueTextSize(9f);
 +
        set.setDrawValues(false);
 +
        return set;
 +
    }
 +
 
 +
    @Override
 +
    public void onValueSelected(Entry e, Highlight h) {
 +
        Log.i("Entry selected", e.toString());
 +
    }
 +
 
 +
    @Override
 +
    public void onNothingSelected(){
 +
        Log.i("Nothing selected", "Nothing selected.");
 +
    }
 +
 
 +
}
 +
Ok so let Android Studio resolve the imports for you.
 +
 
 +
Now add the chart layout to our activity_main.xml, by using the text editor. Add the below code under our <TextView layout:
 +
 
 +
<com.github.mikephil.charting.charts.LineChart
 +
        android:id="@+id/chart"
 +
        android:layout_width="match_parent"
 +
        android:layout_height="match_parent" />
 +
Go back to the Design editor, and rearrange the layouts accordingly (or maybe any other way you want).
 +
 
 +
layout.JPG
 +
 
 +
So as we can see, our chart is a Line Chart with an ID chart​, which we’ll reference in our Java code. Okay, so we want this chart to update in real time when ever a new data arrives from our MQTT Publisher.
 +
 
 +
Now let’s modify our MainActivity.java, first import our ChartHelper​:
 +
 
 +
import helpers.ChartHelper;
 +
and declare a ChartHelper and LineChart object below our MqttHelper object declaration.
 +
 
 +
ChartHelper mChart;
 +
LineChart chart;
 +
Below our dataReceived initialization in the onCreate function, initialize our chart​ and our ChartHelper​
 +
 
 +
chart = (LineChart) findViewById(R.id.chart);
 +
mChart = new ChartHelper(chart);
 +
And in our Mqtt callback, add the below line in our messageArrived function:
 +
 
 +
mChart.addEntry(Float.valueOf(mqttMessage.toString()));
 +
So all in all, our code will look like this:
 +
 
 +
import helpers.ChartHelper;
 +
import helpers.MqttHelper;
 +
 
 +
public class MainActivity extends AppCompatActivity {
 +
 
 +
    MqttHelper mqttHelper;
 +
    ChartHelper mChart;
 +
    LineChart chart;
 +
 
 +
    TextView dataReceived;
 +
 
 +
    @Override
 +
 
 +
    protected void onCreate(Bundle savedInstanceState) {
 +
        super.onCreate(savedInstanceState);
 +
        setContentView(R.layout.activity_main);
 +
 
 +
        dataReceived = (TextView) findViewById(R.id.dataReceived);
 +
        chart = (LineChart) findViewById(R.id.chart);
 +
        mChart = new ChartHelper(chart);
 +
 
 +
        startMqtt();
 +
    }
 +
 
 +
    private void startMqtt(){
 +
        mqttHelper = new MqttHelper(getApplicationContext());
 +
        mqttHelper.mqttAndroidClient.setCallback(new MqttCallbackExtended() {
 +
            @Override
 +
            public void connectComplete(boolean b, String s) {
 +
                Log.w("Debug","Connected");
 +
            }
 +
 
 +
            @Override
 +
            public void connectionLost(Throwable throwable) {
 +
 
 +
            }
 +
 
 +
            @Override
 +
            public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
 +
                Log.w("Debug",mqttMessage.toString());
 +
                dataReceived.setText(mqttMessage.toString());
 +
                mChart.addEntry(Float.valueOf(mqttMessage.toString()));
 +
            }
 +
 
 +
            @Override
 +
            public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
 +
 
 +
            }
 +
        });
 +
    }
 +
}
 +
Now run our app.
 +
 
 +
Screenshot_2017-05-11-09-33-31-128_com.frost.mqtttutorial
 +
 
 +
Holy crap it moves! Feel like a magician now?
 +
 
 +
Thank you, you’ve reached the end! If you had a wonderful time finishing this tutorial, please await for more tutorials from me.
 +
 
 +
https://github.com/wildan2711/mqtt-android-tutorial
 +
 
 +
Wildan Maulana Syahidillah
  
  
Line 179: Line 513:
 
==Referensi==
 
==Referensi==
  
 +
* https://wildanmsyah.wordpress.com/2017/05/11/mqtt-android-client-tutorial/
 
* https://medium.com/swlh/android-and-mqtt-a-simple-guide-cb0cbba1931c
 
* https://medium.com/swlh/android-and-mqtt-a-simple-guide-cb0cbba1931c
 
* https://www.eclipse.org/paho/index.php?page=clients/android/index.php
 
* https://www.eclipse.org/paho/index.php?page=clients/android/index.php

Revision as of 10:01, 24 March 2022

MQTT is one of the popular data communication or messaging protocols that are becoming widely used for machine-to-machine (M2M) communication, or the computer network trend that is popularly coined as “Internet of Things”. MQTT (Message Queue Transport Telemetry) is a messaging protocol with a publish-subscribe pattern, one of the messaging protocol regarded as “light-weight”, which is important for the Internet of Things architecture, because it heavily involves low-powered devices, such as sensor nodes, microcontrollers, and the like.

One of the many uses of the MQTT protocol is to send sensor data from embedded devices. Sometimes we want those data to be sent to our smartphones which could help us monitor some important things from afar, and that’s what I’ll be showing you here in this tutorial, specifically using the Android OS.

If you’re too busy or too lazy to be reading this tutorial, you can get the full code in my Github page.

Software used in this tutorial, be sure to have them installed: -Android Studio -Python 2.7

Outline what I will be covering: 1. Sorting out MQTT dependencies in Android Studio 2. Setting up a cloud MQTT broker in CloudMQTT 3. Setting up a basic MQTT client and service in Android 4. Setting up a mock data publisher with Python 5. Visualizing data with MPAndroidCharts library


You may skip some steps if you think you know what you’re doing. I’ve structured this tutorial as modular as possible so you can replace any of the steps with your own way.

1. Dependecies

First we need to sort out dependencies which are libraries needed to setup an MQTT client and service in an Android app. We will be using the Paho MQTT Client and Android Service provided by Eclipse.

According to official Paho Eclipse Github, we can install the library to our app using Maven, Gradle, or from source, but in this tutorial I will be using Gradle. So to sort out the dependency, simply add these lines to our build.gradle of our Android Studio project.

repositories {

   maven {
       url "https://repo.eclipse.org/content/repositories/paho-snapshots/"
   }

} And in our build.gradle of our Android app.

dependencies {

   compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'
   compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'

} A notification will come up about our Gradle configuration being changed, if so click Sync Now. Oh, and please to have your internet on in this process or the Gradle sync will fail.

Well that’s it, eventually Gradle will converge our Android app with the Paho MQTT Client and Android Service libraries.

2. Setting up cloud MQTT broker with CloudMQTT

There are a couple of cloud MQTT brokers that are available in the internet right now, like CloudMQTT and HiveMQ, but in this tutorial, I will teach how to set up a free private cloud MQTT broker using CloudMQTT. You can sign up for a free account at https://customer.cloudmqtt.com/login, just follow the instructions to sign up.

cloudmqtt

Next, you will be redirected to the CloudMQTT instances page. Click the + Create button to create a new CloudMQTT instance. Now you would be in the Create new CloudMQTT instance page. Insert an instance name, choose any Data center available, and make sure for the “Plan” choose the “Cute Cat”, because we like free stuff.

instance

In the next page, you can see your new created instance, click the details button of that instance. This is what you will see, (never mind the red scribbles):

account

Done! We have our own private cloud MQTT broker. Later we will use those credentials to connect our MQTT Android client to the broker.

3. Setting up MQTT Client and Service

Before we code anything, we need to set up our AndroidManifest.xml file to let our app have permissions to access the Internet, access the network state, and let our app to stay alive as a service. Add these lines before the opening <application tag in our AndroidManifest.xml.

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> And register our MQTT Android Service in our app before the closing application tag using this piece of code:

<service android:name="org.eclipse.paho.android.service.MqttService" />

So it should look like this: manifest Ok great, now lets begin to code shall we.

So first, lets create a new package called helpers in our app, which will contain our MQTT helper class. Then create a new Java Class inside that package called MQTTHelper.

Add the following code inside our MqttHelper class, please adjust the credentials (serverUri, username, password) accordingly with your CloudMQTT instance from the previous step. Please note: the format of the server URI is tcp://server:port

public class MqttHelper {

   public MqttAndroidClient mqttAndroidClient;
   final String serverUri = "tcp://m12.cloudmqtt.com:11111";
   final String clientId = "ExampleAndroidClient";
   final String subscriptionTopic = "sensor/+";
   final String username = "xxxxxxx";
   final String password = "yyyyyyyyyy";
   public MqttHelper(Context context){
       mqttAndroidClient = new MqttAndroidClient(context, serverUri, clientId);
       mqttAndroidClient.setCallback(new MqttCallbackExtended() {
           @Override
           public void connectComplete(boolean b, String s) {
               Log.w("mqtt", s);
           }
           @Override
           public void connectionLost(Throwable throwable) {
           }
           @Override
           public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
               Log.w("Mqtt", mqttMessage.toString());
           }
           @Override
           public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
           }
       });
       connect();
   }
   public void setCallback(MqttCallbackExtended callback) {
       mqttAndroidClient.setCallback(callback);
   }
   private void connect(){
       MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
       mqttConnectOptions.setAutomaticReconnect(true);
       mqttConnectOptions.setCleanSession(false);
       mqttConnectOptions.setUserName(username);
       mqttConnectOptions.setPassword(password.toCharArray());
       try {
           mqttAndroidClient.connect(mqttConnectOptions, null, new IMqttActionListener() {
               @Override
               public void onSuccess(IMqttToken asyncActionToken) {
                   DisconnectedBufferOptions disconnectedBufferOptions = new DisconnectedBufferOptions();
                   disconnectedBufferOptions.setBufferEnabled(true);
                   disconnectedBufferOptions.setBufferSize(100);
                   disconnectedBufferOptions.setPersistBuffer(false);
                   disconnectedBufferOptions.setDeleteOldestMessages(false);
                   mqttAndroidClient.setBufferOpts(disconnectedBufferOptions);
                   subscribeToTopic();
               }
               @Override
               public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                   Log.w("Mqtt", "Failed to connect to: " + serverUri + exception.toString());
               }
           });


       } catch (MqttException ex){
           ex.printStackTrace();
       }
   }


   private void subscribeToTopic() {
       try {
           mqttAndroidClient.subscribe(subscriptionTopic, 0, null, new IMqttActionListener() {
               @Override
               public void onSuccess(IMqttToken asyncActionToken) {
                   Log.w("Mqtt","Subscribed!");
               }
               @Override
               public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                   Log.w("Mqtt", "Subscribed fail!");
               }
           });
       } catch (MqttException ex) {
           System.err.println("Exception whilst subscribing");
           ex.printStackTrace();
       }
   }

} Now go to our activity_main.xml or any other activity you may have created, and you may see the Hello World text view which is added by default by Android Studio, let’s give it an ID so we can access it in our Java code, here I give it an ID dataReceived.

id

So our MainActivity java class should look like this, let Android Studio resolve the imports if needed.

public class MainActivity extends AppCompatActivity {

   MqttHelper mqttHelper;
   TextView dataReceived;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dataReceived = (TextView) findViewById(R.id.dataReceived);
        startMqtt();
   }
   private void startMqtt(){
        mqttHelper = new MqttHelper(getApplicationContext());
        mqttHelper.setCallback(new MqttCallbackExtended() {
            @Override
            public void connectComplete(boolean b, String s) {
            }
            @Override
            public void connectionLost(Throwable throwable) {
            }
            @Override
            public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
                Log.w("Debug",mqttMessage.toString());
                dataReceived.setText(mqttMessage.toString());
            }
            @Override
            public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
            }
        });
    }

So what we do here is set the callback functions for the MQTT client, the only callback function we need right now is the messageArrived function which is called every time the MQTT client receives a message. Whenever a message is received, it will set the text of our TextView in our activity according to the message received.

Now let’s try to send some data to our app.

Go back to your CloudMQTT instance and open the Websocket UI. In the topic input, enter the topic set in our app, in my case is “sensor/+”. If you have done your research about MQTT, you may recall that the “+” sign is a wildcard which simply means “any subtopic”. So let’s try sending data with the “sensor/temp” topic.

send

And in our app:

Screenshot_2017-05-11-05-35-49-859_com.frost.mqtttutorial

Magic! It’s like our computer and phones are telepathic right now!

Well, so far, our Android Project structure will look like this:

structure

4. Setting up a mock data publisher with python

So now since we can be sure that our Android App can communicate using the MQTT Protocol, we can use any device with any programming language to communicate our app with the MQTT Protocol, be it your own web server, your Arduino temperature monitoring device, or even your very own smart underwear! All you need is an MQTT broker, which we have covered in step 2.

Here I will show you an example of that case, by making a mock data publisher using python 2.7.

First you will need to install the paho.mqtt client library in python.

pip install paho-mqtt

And the code:

import json import paho.mqtt.client as mqtt import random import time import threading import sys

mqttc = mqtt.Client("client1", clean_session=False) mqttc.username_pw_set("#User", "#password") mqttc.connect("#Server", #port, 60)


def pub():

   mqttc.publish("sensor/temp", payload=random.normalvariate(30, 0.5), qos=0)
   threading.Timer(1, pub).start()

pub() Please change the credentials accordingly with your CloudMQTT Instance (the hash tagged words). This piece of code will periodically (every 1 second) publish a random number somewhere around 30.

After running the above python code, you will see that your app will show a different number every second, which means the code works.

5. Visualizing data with MPAndroidCharts library

Usually for sensor data monitoring, just showing the raw data may look ugly, like your sister HAH (sorry bad joke), and that is why it may be a good idea to visualize our data using charts. Here I will use one of the most popular chart libraries available for Android, when I mean popular, I mean “the first search result on Google” popular. Try googling “android chart”, and you will find MPAndroidChart as the first result (not the ads, psst).

First things first, dependencies dependencies…

In our project’s build.gradle below the previous MQTT dependency, add `

maven { url "https://jitpack.io" } So now it should look like:

maven {

   url "https://repo.eclipse.org/content/repositories/paho-snapshots/"

} maven { url "https://jitpack.io" } and our app’s build.gradle add it below our MQTT dependency too:

compile 'com.github.PhilJay:MPAndroidChart:v3.0.2' so now it should look like:

compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' compile 'com.github.PhilJay:MPAndroidChart:v3.0.2' Okay gradle, Sync Now!!

Now add a new Java Class in our helpers package named ChartHelper​, with the following code inside:

public class ChartHelper implements OnChartValueSelectedListener {

   private LineChart mChart;
   public ChartHelper(LineChart chart) {
       mChart = chart;
       mChart.setOnChartValueSelectedListener(this);
       // no description text
       mChart.setNoDataText("You need to provide data for the chart.");
       // enable touch gestures
       mChart.setTouchEnabled(true);
       // enable scaling and dragging
       mChart.setDragEnabled(true);
       mChart.setScaleEnabled(true);
       mChart.setDrawGridBackground(false);
       // if disabled, scaling can be done on x- and y-axis separately
       mChart.setPinchZoom(true);
       // set an alternative background color
       mChart.setBackgroundColor(Color.WHITE);
       mChart.setBorderColor(Color.rgb(67,164,34));


       LineData data = new LineData();
       data.setValueTextColor(Color.WHITE);
       // add empty data
       mChart.setData(data);
       // get the legend (only possible after setting data)
       Legend l = mChart.getLegend();
       // modify the legend ...
       // l.setPosition(LegendPosition.LEFT_OF_CHART);
       l.setForm(Legend.LegendForm.LINE);
       l.setTypeface(Typeface.MONOSPACE);
       l.setTextColor(Color.rgb(67, 164, 34));
       XAxis xl = mChart.getXAxis();
       xl.setTypeface(Typeface.MONOSPACE);
       xl.setTextColor(Color.rgb(67, 164, 34));
       xl.setDrawGridLines(false);
       xl.setAvoidFirstLastClipping(true);
       xl.setEnabled(true);
       YAxis leftAxis = mChart.getAxisLeft();
       leftAxis.setTypeface(Typeface.MONOSPACE);
       leftAxis.setTextColor(Color.rgb(67, 164, 34));
       leftAxis.setDrawGridLines(true);
       YAxis rightAxis = mChart.getAxisRight();
       rightAxis.setEnabled(false);
   }
   public void setChart(LineChart chart){ this.mChart = chart; }
   public void addEntry(float value) {
       LineData data = mChart.getData();
       if (data != null){
           ILineDataSet set = data.getDataSetByIndex(0);
           // set.addEntry(...); // can be called as well
           if (set == null) {
               set = createSet();
               data.addDataSet(set);
           }
           data.addEntry(new Entry(set.getEntryCount(),value),0);
           Log.w("chart", set.getEntryForIndex(set.getEntryCount()-1).toString());
           data.notifyDataChanged();
           // let the chart know it's data has changed
           mChart.notifyDataSetChanged();
           // limit the number of visible entries
           mChart.setVisibleXRangeMaximum(10);
           // mChart.setVisibleYRange(30, AxisDependency.LEFT);
           // move to the latest entry
           mChart.moveViewTo(set.getEntryCount()-1, data.getYMax(), YAxis.AxisDependency.LEFT);
           // this automatically refreshes the chart (calls invalidate())
           // mChart.moveViewTo(data.getXValCount()-7, 55f,
           // AxisDependency.LEFT);
       }
   }
   private LineDataSet createSet() {
       LineDataSet set = new LineDataSet(null, "Data");
       set.setAxisDependency(YAxis.AxisDependency.LEFT);
       set.setColor(Color.rgb(67, 164, 34));
       //set.setCircleColor(Color.WHITE);
       set.setLineWidth(2f);
       //set.setCircleRadius(4f);
       set.setFillAlpha(65);
       set.setFillColor(Color.rgb(67, 164, 34));
       set.setHighLightColor(Color.rgb(67, 164, 34));
       set.setValueTextColor(Color.rgb(67, 164, 34));
       set.setValueTextSize(9f);
       set.setDrawValues(false);
       return set;
   }
   @Override
   public void onValueSelected(Entry e, Highlight h) {
       Log.i("Entry selected", e.toString());
   }
   @Override
   public void onNothingSelected(){
       Log.i("Nothing selected", "Nothing selected.");
   }

} Ok so let Android Studio resolve the imports for you.

Now add the chart layout to our activity_main.xml, by using the text editor. Add the below code under our <TextView layout:

<com.github.mikephil.charting.charts.LineChart

       android:id="@+id/chart"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

Go back to the Design editor, and rearrange the layouts accordingly (or maybe any other way you want).

layout.JPG

So as we can see, our chart is a Line Chart with an ID chart​, which we’ll reference in our Java code. Okay, so we want this chart to update in real time when ever a new data arrives from our MQTT Publisher.

Now let’s modify our MainActivity.java, first import our ChartHelper​:

import helpers.ChartHelper; and declare a ChartHelper and LineChart object below our MqttHelper object declaration.

ChartHelper mChart; LineChart chart; Below our dataReceived initialization in the onCreate function, initialize our chart​ and our ChartHelper​

chart = (LineChart) findViewById(R.id.chart); mChart = new ChartHelper(chart); And in our Mqtt callback, add the below line in our messageArrived function:

mChart.addEntry(Float.valueOf(mqttMessage.toString())); So all in all, our code will look like this:

import helpers.ChartHelper; import helpers.MqttHelper;

public class MainActivity extends AppCompatActivity {

   MqttHelper mqttHelper;
   ChartHelper mChart;
   LineChart chart;
   TextView dataReceived;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       dataReceived = (TextView) findViewById(R.id.dataReceived);
       chart = (LineChart) findViewById(R.id.chart);
       mChart = new ChartHelper(chart);
       startMqtt();
   }
   private void startMqtt(){
       mqttHelper = new MqttHelper(getApplicationContext());
       mqttHelper.mqttAndroidClient.setCallback(new MqttCallbackExtended() {
           @Override
           public void connectComplete(boolean b, String s) {
               Log.w("Debug","Connected");
           }
           @Override
           public void connectionLost(Throwable throwable) {
           }
           @Override
           public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
               Log.w("Debug",mqttMessage.toString());
               dataReceived.setText(mqttMessage.toString());
               mChart.addEntry(Float.valueOf(mqttMessage.toString()));
           }
           @Override
           public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
           }
       });
   }

} Now run our app.

Screenshot_2017-05-11-09-33-31-128_com.frost.mqtttutorial

Holy crap it moves! Feel like a magician now?

Thank you, you’ve reached the end! If you had a wonderful time finishing this tutorial, please await for more tutorials from me.

https://github.com/wildan2711/mqtt-android-tutorial

Wildan Maulana Syahidillah


Referensi