TCL, MQTT, and Mosquitto - Part 2

Photo by GraphiDA on Unsplash

TCL, MQTT, and Mosquitto - Part 2

The Internet of Things with Tcl

Connecting to a Remote MQTT Server/Broker

Research and Discovery

The next challenge is using my MQTT Application to connect to a remote server and subscribe to a topic. I use the latest Tcl MQTT client and Tcl MQTT server libraries from chiselapp.com.

My first attempt to connect to the Mosquitto MQTT test site (test.mosquitto.org) on port 1883 was not a success, as indicated by the message on the MQTT-Broker Log window:

{Connecting to test.mosquitto.org on port 1883} {Sending CONNECT (p4, c1, k60)} {Failed to connect to broker: unexpected event: EOF}

I need to delve further into the "Failed to connect to broker: unexpected event: EOF" error. That it attempted to make the connection at all is still good news.

I subsequently succeeded in making a connection using port 8080. I then subscribed to the topic /# (not a recommended practice in the real world!), and the broker/subscription text widgets immediately flooded with messages!

I reloaded my MQTT application and successfully subscribed to three topics on the test.mosquitto.org site: /test/#, /cars/#, and /weather/#. The results are pictured below:

image.png

Connecting to an MQTT Broker/Server (Host)

First, we need to create a client instance using either mqtt new or mqtt create. The client instance becomes the "command" to communicate with an MQTT broker.

The syntax for the command to connect to an MQTT broker is as follows: cmd connect ?-properties properties? name ?host? ?port? where, cmd = client instance (see above), name = MQTT ClientID, host = localhost (default) or URL, and port = 1883 (default).

If the name is an empty string, the broker will assign a ClientID. The AssignedClientIdentifier key reports the given Client ID in the properties argument passed to the callback for the $SYS/local/connection topic.

The default host is "localhost." However, we can specify a different host such as "test.mosquitto.org" used in our test case above.

The default port is 1883. We can change the port we want to use depending on our immediate requirements. In our case, we successfully connected to port 8080, which is a web socket. The test.mosquitto.org website uses the following port numbers:

1883: MQTT, unencrypted, unauthenticated
1884: MQTT, unencrypted, authenticated
8883: MQTT, encrypted, unauthenticated
8884: MQTT, encrypted, client certificate required
8885: MQTT, encrypted, authenticated
8886: MQTT, encrypted, unauthenticated
8887: MQTT, encrypted, server certificate deliberately expired
**8080: MQTT over WebSockets, unencrypted, unauthenticated**
8081: MQTT over WebSockets, encrypted, unauthenticated
8090: MQTT over WebSockets, unencrypted, authenticated
8091: MQTT over WebSockets, encrypted, authenticated

Properties

The following properties may be passed to a connect call:

SessionExpiryInterval: Integer representing the Session Expiry Interval in seconds.

ReceiveMaximum: Integer specifying a limit for the number of QoS 1 and QoS 2 publications that the server is willing to process concurrently for the client.

MaximumPacketSize: Integer representing the maximum packet size the server is willing to accept.

TopicAliasMaximum: Integer indicating the highest value that the server will accept as a Topic Alias sent by the client.

RequestResponseInformation: Boolean to request the server's Response Information in the CONNACK.

RequestProblemInformation: Boolean to indicate whether the server may send Reason String or User Properties in the case of failures.

UserProperty: Name-value pair used to provide additional information to the remote party. The UserProperty key can appear multiple times to represent multiple name-value pairs. The same name is allowed to appear more than once. If necessary, discard this property to keep the message below the maximum packet size specified by the receiver.

Connection Code

The code snippet below contains "puts" statements for debugging and informational purposes. The client parameter is a global variable used to store the new MQTT instance.

Note that this code does not accommodate all possible connection options.

proc RemoteConnect { client username host port protocol} {

    puts "Execute proc RemoteConnect."

    upvar #0 $client remoteClient
    upvar #0 $client remoteUserName
    upvar #0 $host remoteHost
    upvar #0 $port remotePort
    upvar #0 $protocol remoteProtocol

    puts "Parameter Client = $client = $remoteClient"
    puts "Parameter Username = $username = $remoteUserName"
    puts "Parameter Host ($::Remote(host) = $host = $remoteHost"
    puts "Parameter Port ($::Remote(port) = $port = $remotePort"
    puts "Parameter Port ($::Remote(protocol) = $protocol = $remoteProtocol"

    # Get MQTT Client Instance
    if { [set returnCode [catch { mqtt new -clean on -protocol $remoteProtocol } result returnOptions ]] } {
        puts "Error ($returnCode) RemoteConnect:  $result"
        dict for { returnOption returnValue } $returnOptions {
            puts "${returnOption}: $returnValue"
        }
    } else {
        puts "No Errors - Reporting ONLY!"
        puts "($returnCode) RemoteConnect:  $result"
        set remoteClient $result
        dict for { returnOption returnValue } $returnOptions {
            puts "${returnOption}: $returnValue"
        }
        puts "Remote client:  $remoteClient"
        puts "Remote(client):  $::Remote(client)"

        # Connect to Host
        if { [set returnCode [ catch { $remoteClient connect $remoteUserName $remoteHost $remotePort } result returnOptions ]] } {
            puts "Error ($returnCode) RemoteConnect:  $result"
            dict for { returnOption returnValue } $returnOptions {
            puts "${returnOption}: $returnValue"
            }
        } else {
            puts "No Errors - Reporting ONLY!"
            puts "($returnCode) RemoteConnect:  $result"
            dict for { returnOption returnValue } $returnOptions {
                puts "${returnOption}: $returnValue"
            }
        }
    }
    puts "Exiting proc RemoteConnect."
}

Subscribe Code

The following code snippet contains additional statements for debugging and informational purposes. The client parameter is a global variable for the given MQTT instance.

proc RemoteSubscribe { client topic } {
    puts "Execute RemoteSubscribe"

    upvar #0 $client remoteClient
    upvar #0 $topic remoteTopic

    puts "client: $client = $remoteClient = $::Remote(client)"
    puts "Topic:  $topic = $remoteTopic"

    if { [set returnCode [catch {$remoteClient subscribe $remoteTopic putCLIENTmsgs} result returnOptions]] } {
        puts "ERROR ($returnCode) - RemoteSubscribe:  $result"
        # returnOptions Dictionary:  -errorinfo -errorcode -errorline, -errorstack
        dict for { returnOption returnValue } $returnOptions {
            puts "${returnOption}: $returnValue"
        }
    } else {
        puts "No Errors - Reporting ONLY!"
        puts "($returnCode) RemoteSubscribe:  $result"
        dict for { returnOption returnValue } $returnOptions {
            puts "${returnOption}: $returnValue"
        }
        puts "Connected to $remoteClient"
        puts "Subscribed to $remoteTopic"
    }
}

Next Steps

I need to add the ability to publish messages and manage more than one client instance, with each subscribing to multiple topics.

A future feature includes the ability to save the current connection settings to a "config file."

I also need to expand the number of settings for a given MQTT Client Instance to accommodate all available options.

Did you find this article valuable?

Support Redge Shepherd by becoming a sponsor. Any amount is appreciated!