Sunday, October 29, 2017

MQTT: Publishing and Subscribing with Node.js

This short tutorial demonstrates how to communicate with MQTT using Node.js. We will:

 

MQTT.js Library

A very popular MQTT library for Node.js is MQTT.js, and for good reasons:

  • it is actively supported
  • it supports most features of MQTT 3.1.1
  • it has good documentation
  • it can be used in async mode
  • and it works in a browser, too!

We'll be using this library.

 

Setup Test Environment

We will be using the same MTT topic from an earlier post: home/living-room/temperature

We need three Terminal windows:

  1. In the first one, we start the broker using the command:
    mosquitto
    Notice I omitted the -v verbose flag.
  2. In the second window, we start a subscriber listening to the topic
    mosquitto_sub -h localhost -t home/living-room/temperature
    Notice that I removed the -d debug flag. This way output will be cleaner.
  3. In the third Terminal window, publish a message using
    mosquitto_pub -d -h localhost -t home/living-room/temperature -m 'Hello, MQTT!'
    and the message should appear in the second window.

This is how those three windows should look:

 

Setup a Node.js Project

Now create a Node.js app using these steps:

  1. Open yet another Terminal window
  2. Create a folder named "MQTT with NodeJS":
    mkdir "MQTT with NodeJS"
  3. Move into that window with:
    cd "MQTT with NodeJS"
  4. Create a simple Node.js project with:
    npm init
  5. Answer some questions that will create a basic package.json
    - give the project the name "mqtt-with-node-js"
  6. Accept the default package.json file
  7. Install MQTT.js using
    npm install mqtt --save

This last command will create a subfolder called node_modules and a package.json file that looks something like this:

{
  "name": "mqtt-with-node-js",
  "version": "1.0.0",
  "description": "Demonstrates how to communicate with MQTT",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "MQTT",
    "NodeJS"
  ],
  "author": "Mike Klepper",
  "license": "ISC",
  "dependencies": {
    "mqtt": "^2.12.0"
  }
}

 

Publishing to a MQTT Topic

Create a new file called publish.js in the same folder as package.js, and edit it to read:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * publish.js
 *
 * By: Mike Klepper
 * Date: 29 October 2017
 *
 * This program demonstrates how to connect to MQTT
 *
 * See blog post on patriot-geek.blogspot.com
 * for instructions.
 */

const BROKER_URL = "mqtt://localhost:1883";
const TOPIC_NAME = "home/living-room/temperature";
const CLIENT_ID = "publish.js";

var MQTT = require("mqtt");
var client  = MQTT.connect(BROKER_URL, {clientId: CLIENT_ID});

client.on("connect", onConnected);

function onConnected()
{
  client.publish(TOPIC_NAME, "Hello MQTT from NodeJS!");
  client.end();
}

Here's the walkthrough:

13-15 Declare constants for the broker address, topic, and client name. Note that the address must specify the "mqtt" protocol!
17 Import the MQTT.js package
18 Create a client and connect to the broker
20 Create a listener that waits for CONACK, then calls the callback named onConnected
22 Once we're connected...
24 ...publish a message to the topic and...
25 ...close the connection.

What QoS does the MQTT.js library use when publishing? By default it is QoS 0. We can change that by modifying line 24 to read:

24
client.publish(TOPIC_NAME, "Hello MQTT from NodeJS!", {qos: 1});

We can further simplify the code by omitting the clientId from the connect statement, in which case that line will read

18
var client = MQTT.connect(BROKER_URL);

Notice that we assume that this line

24
client.publish(TOPIC_NAME, "Hello MQTT from NodeJS!");
finishes before we close the connection.
25
client.end();
Fortunately, the MQTT.js library does handle that situation by default.

While that code was extremely easy to read, it is only "happy path" code - there is no error handling! The library allows for this by providing callbacks for client.publish client.end and these callbacks can be used to catch and handle errors.

 

Subscribing to Single or Multiple Topics

The next program demonstrates how to subscribe to single or multiple topics. First, create a file called subscribe.js and modify it to read:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
 * subscribe.js
 *
 * By: Mike Klepper
 * Date: 29 October 2017
 *
 * This program demonstrates how to subscribe to MQTT
 *
 * See blog post on patriot-geek.blogspot.com
 * for instructions.
 */

const BROKER_URL = "mqtt://localhost:1883";
const TOPIC_NAME = "home/living-room/temperature";
const CLIENT_ID = "subscribe.js";

var MQTT = require("mqtt");
var client  = MQTT.connect(BROKER_URL, {clientId: CLIENT_ID});

client.on("connect", onConnected);
client.on("message", onMessageReceived)

function onConnected()
{
  client.subscribe(TOPIC_NAME);
}

function onMessageReceived(topic, message)
{
  console.log(topic);
  console.log(message.toString());
  console.log("");
}

To test this program, run it with

node subscribe.js
At which point nothing appears to happen! Then go to the third Terminal window and publish a message:
mosquitto_pub -d -h localhost -t home/living-room/temperature -m 'Hello, MQTT!'
And the message should be displayed in the window running subscribe.js!

Walkthrough:

20 - 21 Set event handlers for when the client connects and it receives a message
23 When the client connects...
25 ...have it subscribe to the desired topic
28-33 When client receives a message, print the topic, the message, and a new line.

Why is the .toString() method called in line 31? It isn't necessary for printing the topic, since the topic is a string, whereas the the message is a string buffer and not a string. If we removed the toString(), the output will be

home/living-room/temperature
<Buffer 48 65 6c 6c 6f 2c 20 4d 51 54 54 21>

Now let's modify this program to subscribe to wildcard topics. Change line 14 to read:

14
const TOPIC_NAME = "home/living-room/+";

Run the program. In the Third terminal window, enter:

mosquitto_pub -d -h localhost -t home/living-room/temperature -m 'Hello, MQTT! This is the temperature!'

then enter:

mosquitto_pub -d -h localhost -t home/living-room/humidity -m 'Hello, MQTT! This is the humidity!'

In the window with the program running we should see:

home/living-room/temperature
Hello, MQTT! This is the temperature!

home/living-room/humidity
Hello, MQTT! This is the humidity!

So we have indeed subscribed to a path with a wildcard in it!

Let's experiment. Try this command:

mosquitto_pub -d -h localhost -t home/living-room/temperature/fahrenheit -m 'Hello, MQTT! This is the temperature in Fahrenheit'
This is NOT displayed in the window running the program. This is because the "+" wildcard represents only one level in a topic.

Notice that this program continues to run until we press Ctrl-C. This is a good thing, since we usually want subscribers to continuously listen for incoming messages.