Lab11 : Working with MQTT
In this lab we will host a MQTT broker on our development machine, and run clients from: our dev. machine, from Android and from Arduino.
1. Broker & Client on Development Machine / Server
There are several implementations in different languages of the MQTT protocol, both for the server (broker) and client side. We will use mosquitto, an open-source implementation of MQTT to host the broker.
- Download mosquitto for your platform : https://mosquitto.org/download/
At this stage, the broker should be running on your machine, on the default MQTT port 1883. Let's verify that we can connect to the broker as client.
- Subscribe to topic "foo/bar" by running
mosquitto_sub -t "foo/bar"
- The above is equivalent to
mosquitto_sub -h localhost -p 1883 -t "point"
, For an external broker we'd have to provide a different address instead of localhost - The program will keep running and print out any messages published to that topic.
- The above is equivalent to
- Publish a message to the topic by opening a second terminal, and running:
mosquitto_pub -t "foo/bar" -m "Hello, MQTT!"
The message should appear in the 1st terminal.
With MQTT topics, a client can subscribe to messages from multiple topics simultaneously. This material has a good overview.
2. MQTT Client on Android
2.1 Setting up library
- Create a new (Blank Empty Activity) Android Studio Project.
Let's add the Eclipse Paho MQTT Client Library to our project:
- in project .gradle file:
allprojects { repositories { google() jcenter() maven { url "https://repo.eclipse.org/content/repositories/paho-snapshots/" } } }
- in app module .gradle file:
// MQTT client implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.1' implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' // needed to resolve java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/localbroadcastmanager/content/LocalBroadcastManager;
- In AndroidManifest.xml:
‹!-- Mqtt Service --› <service android:name="org.eclipse.paho.android.service.MqttService" />
- MQTT client also needs these permissions:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2.2 Logging MQTT messages
- Add the code from here to your Activity.
- Study the code, run it, verify that it can connect to the broker.
- Update the kotlin code so that once the MQTT client is connected, it subscribes to topic "foo"
Toast.makeText(applicationContext, "MQTT Connected, subscribing...", Toast.LENGTH_SHORT).show() mqttClient.subscribe("foo", 0) // 0 - is the QoS value
- Publish some message to the "foo" topic and verify you can see it being logged.
2.3 Drawing a plot with the data
Let's re-create the Arduino IDE plotter in Android, using a custom View. When creating a custom View, we can control the entire 2D drawing of the UI component down to pixel precision (if necessary), instead of using one of the existing View Widgets (e.g. Button, Spinner, etc) or Layouts.
We will use Android custom drawing capabilities to draw a line graph showing values sent to a MQTT topic.
- Create a new class that extends View, and overrides the constructor with signature (c: Context, atrs: AttributeSet)
class LinePlot(context: Context, attrs: AttributeSet) : View(context, attrs)
- Override onDraw(canvas: Canvas) method
- Using the Canvas object, we can actually draw something onto the screen
- Canvas provides various useful methods such as drawCircle, drawRectangle, drawText, drawLine, drawBitmap, etc.
- To draw something on the canvas, you must specify the coordinates and a Paint object which defines the drawing style (e.g. brush color, thickness).
Now, you place your custom View into a Layout XML, e.g. activity_main.xml:
<ee.ut.cs.lab11.LinePlot android:id="@+id/line_graph" android:layout_width="0dp" android:layout_height="0dp" ... ></ee.ut.cs.lab11.LinePlot>
Note, it's possible to define your XML attributes for your custom View (e.g. LinearLayout had a orientation attribute), see here for more info.
- Add the following to your custom View:
private val bluePaint = Paint().apply { color = Color.BLUE strokeWidth = 8f } // Sample dataset: var dataSet = mutableListOf(3.3f,4.0f,4.5f,6.6f,4.6f,2.6f,1.0f,1.5f,6.7f) var dataSetMax = dataSet.max() /** Helper functions which scale the numeric values to screen dimensions */ private fun scaledX(value : Float) : Float { return (value / dataSet.size) * width } // Y- value is also reversed because 0,0 coordinate is in top-left corner in Canvas private fun scaledY(value : Float) : Float { return height - ((value / dataSetMax!!) * height) } override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) dataSet.forEachIndexed { i, value -> canvas?.drawPoint(scaledX(i.toFloat()), scaledY(value), bluePaint ) } }
Try to run the application. The code creates some sample data and draws a point for each element in the dataSet collection. Note that the numeric values are adjusted based on screen height/width and dataset max value.
- Now, inside onDraw(), write code which would also draw lines connecting each pair of points in the dataset (using canvas.drawLine(x1,y1,x2,y2,paint) ). Try using a different colour paint.
- First try to get the line drawing to work by yourself, but if you need an example, see here
2.4 Joining the MQTT client and plotter
Add this function to your View:
fun addData(value : Float){ dataSet.add(value) if (dataSet.size > 20 ){ dataSet.removeAt(0) // remove oldest (FIFO) } // Update Max, Min dataSetMax = dataSet.max() dataSetMin = dataSet.min() // Force UI refresh: invalidate() }
- Update the MQTT client code so that it invokes the addData function of the custom View when a message arrives.
- You can refer to the View from Activity by its ID and thus access its methods.
- Test that it works by publishing some messages with mosquitto_pub
Arduino MQTT Client
Let's install the library PubSubClient
- Open Tools -> Manage Libraries and find PubSubClient by Nick O'Leary
Open the following Arduino code . This code:
- Establishes a connection to a pre-configured WiFi access point (in setup() )
- Using the WiFi library,
#include <WiFi.h>
- Using the WiFi library,
- Once the WiFi connection is established, it tries to connect to the MQTT broker (end of setup() )
- In the main loop(), every 100ms it reads an analog sensor value and publishes it as a MQTT message
- Every iteration of loop(), mqttClient.loop() is also called, this allows the client to process incoming messages and maintain its connection to the server.
Modify the code accordingly (update WiFi & MQTT Credentials) and run it. Make sure all your devices are in the same network. Depending on your setup, a firewall may be blocking port 1883 (e.g. when creating a hotspot from Windows).
Other information
It is possible to configure authentication and SSL encryption for brokers, but we did not consider those options in this lab.