TCP (and TLS)

MQTT is an IoT friendly messaging protocol for publishing and subscribing to a shared MQTT broker from a wide range of devices.

This tutorial shows you how to connect to an MQTT broker using TCP (or TLS).

Connect

The Toit MQTT client is available from the mqtt package - in the Toit package registry.

Install the package using the Toi CLI:

toit pkg install github.com/toitware/mqtt

and use it by importing it into your application with import mqtt.

MQTT requires every client to have a unique CLIENT_ID. That ID is sent when connecting to the broker, and identifies a session on the broker. It is used to reliably transmit packets that require acknowledgements (QoS=1).

import net
import mqtt

// Include a random number, as we are sharing test.mosquitto.org with other users.
CLIENT_ID ::= "my-client-id-$(random)"
HOST      ::= "test.mosquitto.org"

main:
  network := net.open
  transport := mqtt.TcpTransport network --host=HOST
  client := mqtt.Client --transport=transport
  client.start --client_id=CLIENT_ID

  // Client is now connected.

Some brokers require the client to provide a username and password. These can be passed to the client during the start call in an options object:

  options := mqtt.SessionOptions
      --client_id=CLIENT_ID
      --username=MY_USERNAME
      --password=MY_PASSWORD
  client.start --options=options

See the documentation or examples of the MQTT library for more information.

Publish

Simple sensor applications mainly have one purpose; to send sensor data. When sending data, you simply need to specify which topic to publish to, together with the payload.

A publish function could look similar to the following:

import mqtt
import encoding.json

TOPIC ::= "my/topic"

publish client/mqtt.Client value/float:
  payload := json.encode {
    "value": value
  }
  client.publish TOPIC payload

Note that the payload must be a byte array. If your data is a string, simply call .to_byte_array on it.

Subscribe

Subscribing to data requires two pieces of information: the topic (or filter) to subscribe to, and the callback that should be called when a message arrives.

For example:

import mqtt
import encoding.json

TOPIC ::= "my/topic"

subscribe client/mqtt.Client:
  client.subscribe TOPIC:: | topic/string payload/ByteArray |
    decoded := json.decode payload
    print "Received value on '$topic': $(decoded["value"])"

Here, we decode the incoming payload with the JSON decoder. If the data payload should just be interpreted as a string, you can simply call payload.to_string instead.

Since MQTT clients can have a session on the server, we could receive packets for previously subscribed topics as soon as the client connects. If the subscribe function wasn't called yet, then the callback wouldn't be invoked for these early messages. To avoid losing packets, the client can also be configured with routes at construction time:

main:
  network := net.open
  transport := mqtt.TcpTransport.tls network --host=HOST
      --root_certificates=[ certificate_roots.ISRG_ROOT_X1 ]
  routes := {
    TOPIC: :: | topic/string payload/ByteArray |
      decoded := json.decode payload
      print "Received value on '$topic': $(decoded["value"])"
  }
  client := mqtt.Client --transport=transport --routes=routes
  client.start --client_id=CLIENT_ID
  // Client is now connected and subscribed to the given routes.

TLS

The TcpTransport mentioned above can also be used to connect to secure servers. For that we need root certificatse which are then used to authenticate the server.

Here, we are using the certificate-roots package which can be installed with

toit pkg install github.com/toitware/toit-cert-roots

In other cases, the certificate might be given by the server and should be pasted into the sources.

Once we have the root certificates, the client's transport can be created as follows:

import mqtt
import net
import net.x509
import certificate_roots

// Include a random number, as we are sharing test.mosquitto.org with other users.
CLIENT_ID ::= "my-client-id-$(random)"
HOST      ::= "test.mosquitto.org"
PORT      ::= 8886

main:
  network := net.open
  transport := mqtt.TcpTransport.tls network --host=HOST --port=PORT
      --root_certificates=[ certificate_roots.ISRG_ROOT_X1 ]
  client := mqtt.Client --transport=transport
  client.start --client_id=CLIENT_ID
  // Client is now connected.