Using MQTT in Common Lisp with ABCL and the Eclipse Paho client.

2013-07-10

I have recently written some posts about using Clojure to take advantage of Java libraries. The idea of using a Lisp language that can take advantage of existing Java infrastructure is something that can however be achieved in different ways, Clojure being but one of them.

On the Scheme side I know at least GNU Kawa and SISC, and on the Common Lisp side Armed Bear Common Lisp (ABCL). Since I lean towards Common Lisp more than Scheme, I have made some experiences with ABCL (including some comparisons with Clojure in terms of syntax which I will likely post about since the original content, sent to the ABCL mailing list as a pastebin link, is no longer accessible).

This is the simple MQTT example, which I previously wrote in Clojure, written in Common Lisp and using ABCL Java interop (gist here):

(defpackage #:abcl-mqtt
  (:use #:cl))

(require :abcl-contrib)
(require :jss)
(in-package #:abcl-mqtt)

(defparameter broker-url "tcp://m2m.eclipse.org:1883") ; Eclipse sandbox server, see http://m2m.eclipse.org/sandbox.html for details on use
(defparameter mqtt-topic-name "abcltest")
(defparameter mqtt-message (or (car EXT:*COMMAND-LINE-ARGUMENT-LIST*) ; If there is no command-line argument then use a quote from Pessoa's Tobacco Shop
                   "But at least, out of my bitterness at what I'll never be, There's the quick calligraphy of these lines, The broken archway to the Impossible."))
(defparameter client-id "FSM-ABCL-Test")

(defun mqtt-callback ()
  "Function called after delivery confirmation"
  (jss::jinterface-implementation "org.eclipse.paho.client.mqttv3.MqttCallback"
                  "connectionLost"
                  (lambda (cause)
                    (print cause))
                  "messageArrived"
                  (lambda (topic message) ;; not used
                    (print (format nil "Topic: ~A" (#"getName" topic)))
                    (print (format nil "Message: ~A" (#"getPayload" message))))
                  "deliveryComplete"
                  (lambda (token)
                    (print "*** DELIVERY COMPLETE ***"))))

(defun mqtt-connect (topic broker-url client-id)
  "Establishes a MQTT connection to TOPIC; returns the mqtt client object"
  (let ((mqtt-conn-options (jss::new 'MqttConnectOptions))
    (mqtt-client (jss::new 'MqttClient broker-url client-id)))
    (#"setCleanSession" mqtt-conn-options jss::+true+)
    (#"setKeepAliveInterval" mqtt-conn-options 30)
    (print (format nil "Connected to ~A" broker-url))
    (#"setCallback" mqtt-client (mqtt-callback))
    (#"connect" mqtt-client mqtt-conn-options)
    mqtt-client))

(defun mqtt-create-message (message)
  "Creates a MQTT message from MESSAGE, a string"
  (let ((mqtt-message (jss::new 'MqttMessage (#"getBytes" message))))
    (#"setQos" mqtt-message  0)
    (#"setRetained" mqtt-message jss::+false+)
    mqtt-message))

(defun mqtt-publish  (topic message)
  "Publishes MESSAGE to TOPIC"
  (print (format nil "*** PUBLISHING TO TOPIC ~A  ***" (#"toString" topic)))
  (#"waitForCompletion" (#"publish" topic message)))

(defun testmq ()
  "Main demo function, creates the connection and sends the message"
  (let* ((client (mqtt-connect mqtt-topic-name broker-url client-id))
     (topic (#"getTopic" client mqtt-topic-name))
     (message (mqtt-create-message mqtt-message)))
    (mqtt-publish topic message)
    (sleep 0.2)
    (#"disconnect" client)))

;; Make it all work
(testmq)
(cl-user::quit)

The use of ABCL Java facilities is of course essential to use the the Java libraries (see Hans Hübner’s post about using ABCL to parse Excel files using Apache POI for another example of the usefulness of this), so those parts aren’t portable to other CL implementations, but the rest of the code is. This means that by using ABCL one can use CL – which is a standartised language with multiple implementations – for the code, and that even with non-portable parts (which can be isolated) the bulk of the code is implementation independent.

Additionally (and at least equally important) it allows the use of Common Lisp wherever a JVM exists, which in itself is an advantage to those who know the language and do not want to program in Clojure for whatever reason. There are several Java interop options built into ABCL – the example above uses JSS 3, but there are also the included primitives and even Rich Hickey’s pre-Clojure framework JFLI.


This document was generated on December 29, 2023 using texi2any.