As the most prominent protocol for lightweight M2M communication, exploring and understanding the MQTT Paho client library is of paramount importance. For this reason, we have created the MQTT Chat app in python, through we can demonstrate the main features of MQTT communication. For more conceptual understanding of MQTT check M2M communication.
MQTT Chat / Messenger
First of all we would have to import paho-mqtt library:
import paho.mqtt.client as mqtt_client import paho.mqtt.subscribe as mqtt_subscribe
Secondly we will define some commonly used MQTT parameters as variables
client_id = "Student_X" mqtt_host = "broker.hivemq.com" mqtt_port = 1883 publishing_topic = "testtopic/python101-milos/" subscription_topic = "testtopic/python101-milos/" qos = 2 # MQTT QoS level
Here we have defined the parameters for connection to the MQTT broker (Host, Port and ClientID) Additionally we have defined the root of the topics we would use to communicate.
On Event functions
As we already know, protocol handles the communication backend, which creates ‘events’ callbacks. These are the ‘reactions’ to the messages coming from the broker. In practice, paho-mqtt client allows specifying your own functions to handle events (Subscribed, Message arrived, Connected, etc.) This is done in the following way:
def on_message(client, userdata, message): # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>") # print("Received message '" + str(message.payload) + "' on topic '" # + message.topic + "' with QoS " + str(message.qos)) # print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>") global received_messages ## Global required in order to be able to make changes received_messages = received_messages + '\n' + str(message.payload) def on_subscribe(client, userdata, mid, granted_qos): global subscribedFlag ## Global required in order to be able to make changes subscribedFlag = True print(client, " subscribed!") def on_unsubscribe(client, userdata, mid): global subscribedFlag subscribedFlag = False print(client, " unsubscribed!") def on_connect(client, userdata, flags, rc): global connectedFlag connectedFlag = True print(">>>>>>>>>> Success! MQTT connected! >>>>>>>>>>>") def on_disconnect(client, userdata, rc): global connectedFlag connectedFlag = False print(">>>>>>>>>> Attention! MQTT disconnected! >>>>>>>>>>>")
As you can assume, header of the functions is predefined, and you can find more details in the paho-mqtt library documentation. As far as the ‘body’ of event handling functions, there we have full freedom to insert whatever code we need. In this example we are just setting flags on Connect/Disconnect and Subscribe/Unsubscribe, and printing simple console messages as info. In the on_message, from the other hand instead of printing information, we decided to just append the messages to a string var, as that would be the easiest approach for this example.
Connecting to broker
Connecting to the MQTT broker requires two steps:
- Define Client (class)
- Invoke ‘connect’ method with required parameters
# creating MQTT client mqtt = mqtt_client.Client(client_id) # connecting to MQTT server mqtt.connect(host=mqtt_host, port=mqtt_port)
This is quite straight-forward action, which is recommended to be done within ‘Try-Except’ block, so that you can handle any possible exceptions that might arise especially due to network issues.
Now, when we have mqtt class created we can assign callback functions, from our ‘On Event functions’. This is done simply by passing function references:
# Assign event handling functions mqtt.on_connect = on_connect mqtt.on_disconnect = on_disconnect mqtt.on_subscribe = on_subscribe mqtt.on_unsubscribe = on_unsubscribe mqtt.on_message = on_message # define function that executes on message arrival
This is the crucial components, as on it’s execution, the actual command messages are being pushed to the MQTT broker. This means that when you call method ‘connect’ (or any other from the mqtt module) it will not be executed until the network loop has been executed. There are various ways to run the network loop. You can let the loop run in regular intervals (in a parallel thread) by initiating
loop_stop(), or you can run the loop infinitely using
loop_forever() (for apps that should run for a long time periods). Also you can run the loop manually by calling
loop() method. For the purpose of this example we will use
# Executes previous MQTT command by running the network loop More info : https://pypi.org/project/paho-mqtt/#network-loop mqtt.loop_start() # runs the network loop in regular time intervals
More details on loops, read on Paho Python MQTT Client-Understanding The Loop
In this example we will subscribe to two topics, as we want to follow both general and private chat. In addition to the topic we have to provide information on QoS (quality-of-service) level.
mqtt.subscribe( [(subscription_topic + "all", qos), (subscription_topic + client_id, qos)] ) # subscribes to the desired topic
For the purpose of example topic “all” is general chat, and private messages are received through the tomic coresponding tho the client_id
In our example we are using the
input() function to obtain message form the ‘user’. Here it is crucial to remember that this function blocks the program until it receives the input. This is not the best way if you’re talking about IoT, but for demonstration purposes we can work with it. We have also used simple string exploration, so that private messages can be distinguished by format
@milos Hi Milos! (@receiver Message)
msg_1 = input("Type your message (or leave blank) and press Enter! >>> " + '\n') if connectedFlag: # print("DEBUG: Trying to publish message!") if msg_1 != '': ## If we want to send messages to the specific person, we should type message in format "@Student_Y Hello there!" if msg_1 == '@': # detects weather the message is intended for the specific receiver if len(msg_1.split(" ", 1)) > 1: # check if the format is proper: "@receiver message" receiver, msg_1 = msg_1.split(" ", 1) # splits receiver ID from the message receiver = receiver[1:]
In the real IoT scenario, instead
input() we would use some non-blocking function like analogRead() or similar. Also whenever we have code like this we should use Try-Except blocks.
In order to publish our message(s), we should simply execute publish() method, and let the Network Loop do its job
mqtt.publish(topic = publishing_topic + receiver, payload = msg_1, qos = qos )