From the exquisite penmanship of Cosmin Stoian and Geo Apopei

In this article we will dive into the process of setting-up MQTT and all the decisions taken for implementing the broker and Node.js subscriber.

In the realm of IoT and real-time data communication, the choice of protocol plays a crucial role in ensuring reliable and efficient data transmission. Among the plethora of options available, MQTT (Message Queuing Telemetry Transport) stands out as a lightweight and efficient protocol, perfect for scenarios where bandwidth and latency are critical factors. In this article, we’ll explore how we, at Salt&Pepper, set up MQTT in Node.js using TypeScript, and the considerations behind the decisions made during implementing the client’s specs.

 

Setting Up MQTT in Node.js with TypeScript

 

Node.js, with its asynchronous event-driven architecture, pairs seamlessly with MQTT for handling real-time data streams. By utilizing TypeScript, we can enhance code maintainability and ensure type safety throughout the development process.

The first decision we made was regarding Quality of Service (QoS), a crucial aspect of MQTT. QoS determines the level of guarantee for message delivery. After careful consideration, we opted for QoS 0. While QoS levels 1 and 2 offer higher reliability, they come with increased overhead due to acknowledgement mechanisms.

Since the nature of the application allowed for occasional message loss without significant consequences, QoS 0 proved to be the optimal choice, offering lightweight and low-latency communication.

Code snippet:

import { connect } from "mqtt";
import config from "../config";

export const getClient = () => {
  return connect(config.mqtt.addressUrl, {
    ...config.mqtt.options,
  });
};

export default getClient();


mqttClient.subscribe(config.mqtt.topic, { qos: 0 }, (error) => {
  if (error) {
    logger.error("SUBSCRIBE ERROR: " + error);
    return;
  }

  logger.info("Subscribed to mqtt topic:" + config.mqtt.topic);
});

 

Utilizing Topic Single Level Wildcard

 

One of the strengths of MQTT lies in its flexible topic structure, allowing for dynamic routing of messages. We leveraged the single-level wildcard feature of MQTT topics by incorporating the publisher device’s ID of the device as the wildcard value (e.g.: project/+/event). This approach provided a scalable and efficient solution for handling multiple devices within the same MQTT broker.

By structuring topics in this manner, each device could publish and subscribe to a unique topic based on its UUID, ensuring isolation and efficient message routing within the system.

Subscriber code snippet:

export const extractIdFromTopic = (topic: string): string | undefined => {
  const topicParts = topic.split("/");
  // Should be validated at broker level but was added for unit testing
  if (topicParts.length !== 3) {
    logger.error(`Incorrect topic ${topic} with length ${topic.length}`);
    return null;
  }

  // Should be validated at broker level but was added for unit testing
  if (topicParts[0] !== "project" && topicParts[2] !== "event") {
    logger.error(`Incorrect topic ${topic} format`);
    return null;
  }

  logger.info(`Received on Topic: ${topic}`);

  return topicParts[1]?.trim();
};

 

Optimizing Message Structure

 

Efficient data transmission is paramount in IoT applications, where bandwidth and resource constraints are common challenges. To minimize the size footprint of messages, we structured the payload as an array of 9 positions, with each position representing a binary value (1 or 0).

This compact representation not only reduced the data size but also simplified message parsing and decoding on the subscriber side. By encapsulating the information in this format, we ensure optimal utilization of network resources and costs while maintaining the integrity and accuracy of the transmitted data.

 
  const payload = buffPayload.toString();

​​
const isCorrectInputForParameters = (
  value: InputType,
  index: number,
  _array: string[],
): boolean => ["0", "1"].includes(value);

export const extractPayload = (
  payload: string,
): PayloadType | null => {
  logger.info(`Payload: ${payload}`);

  const rawMessage = payload.split(",");
  const message = rawMessage.map((messageItem) => messageItem.replace('"', ""),
  ); // Removing extra "
  const messageLength = message.length;

  if (messageLength !== process.env.ARR_LENGTH) {
    logger.error(`Incorrect length: ${messageLength} and message: ${message}`);
    return null;
  }
  
  const isAllowedInput = message.some(isCorrectInputForParameters);

  if (!isCorrectInputForParameters) {
    logger.error("Incorrect data received for state parameters in array: ", message);
    return null;
  }

  return message;
};

 

Conclusion

 

In conclusion, setting up MQTT in Node.js with TypeScript offers a powerful combination for building scalable and efficient real-time communication systems. By carefully considering factors such as QoS, topic structure, and message optimization, we can create robust solutions tailored to the specific requirements of our IoT applications.

Stay tuned for more articles coming out around IoT Architecture and reach out if you have any questions.