Callbacks are functions that are called in response to an event.
The events and callbacks for the Paho MQTT client are as follows:
- Event Connection acknowledged Triggers the on_connect callback
- Event Disconnection acknowledged Triggers the on_disconnect callback
- Event Subscription acknowledged Triggers the on_subscribe callback
- Event Un-subscription acknowledged Triggers the on_unsubscribe callback
- Event Publish acknowledged Triggers the on_publish callback
- Event Message Received Triggers the on_message callback
- Event Log information available Triggers the on_log callback
The Paho client is designed to only use the callback functions if they exist, and doesn’t provide any default callback functions.
However; it does provide a mechanism to set them.
To use a callback you need to do two things
- Create the callback function
- Assign the function to the callback.
Callback Function Example
As an example we will use the on_connect callback.
First I create my function. I’ve called it on_connect but I could have called it anything I wanted.
Here is the function.
def on_connect(client, userdata, flags, rc): logging.info("Connected flags"+str(flags)+"result code "\ +str(rc)+"client1_id ") client.connected_flag=True
The function simply logs a message, and sets a flag.
Now to associate my function with the On_connect callback I use the following:
Note: If my function had been called myfunction then I would use the following:
Callbacks and the Client Loop
Callbacks are dependent on the client loop as without the loop the callbacks aren’t triggered.
Below is the code of a simple python script that creates a connection and then waits in a loop for the on_connect callback to be triggered.
The on_connect callback then sets the flag that terminates the loop and the script ends.
This means that if the callback isn’t processed then the while loop never ends as we shall see later.
Here is the output generated by the script.
Notice how the loop executes waiting for the callback and stops once the callback gets processed.
So let’s run the above script again, but this time I won’t start the loop. To do that I simply comment out the client.start_loop() line.
Here is what happens when I run the script.
You can see that I had to manually terminate the script as it never processed the callback, and the callback is what terminates the while loop. You can download this script below:
How Callbacks Work
If you dig into the Client code you will find the code for the Connect acknowledge message.
When the client receives a CONNACK message the callback is triggered if it exists.
Here is the relevant section of the code. (around line 276)
Notice the if on_connect statement. This is what checks for the callback, and if it exists it calls the callback- self.on_connect().
The self.on_connect function is the function we assigned earlier using.
You should see from the code that the function call passes several arguments, and so the function we create needs to be able to handle those arguments.
If you look back at the function I created you can see that it handles 4 arguments. The documentation has a description of those arguments
Asynchronous or Synchronous?
If you use the loop_start() or loop_forever functions then the loop runs in a separate thread, and it is the loop that processes the incoming and outgoing messages.
In this case the callbacks can occur any time in your script and are asynchronous.
However if you call the loop() function manually in the script then the callbacks are synchronous with your script as they can only occur when you call the loop() function.
Returning Values from Callbacks
Due to the way they operate It is not possible to return values from a callback using the standard return command.
Therefore if you need to get status information from a callback you need to use some form of global variable in the callback.
You have several options available:
- Declare a global variable using the global statement
- Use a list declared in the main function.
- Use a dictionary declared in the main function.
- Use an Object declared in the main function.
If you refer back to the on_connect call back example I used the connected_flag which is a property of the client object.
Because the client object is available throughout the script it can be accessed anywhere in the script.
So in the main script I use:
## while not client1.connected_flag: print("waiting for connect") time.sleep(0.5)
Which waits for the connection to be established before proceeding.
To access received messages I tend to use a message queue which is thread safe, but you can also use a simple list.
To use a queue you need to import the module and create a queue
## from queue import Queue q=Queue()
then in the on_message callback you place the received message in the queue.
For a list you declare the list outside the on_message callback which means it is available in the callback, there is no need for global statement.
The following code shows both methods in the on_message callback
## def on_message(client1, userdata, message): #global messages m="message received " ,str(message.payload.decode("utf-8")) messages.append(m)#put messages in list q.put(m) #put messages on queue print("message received ",m)
The following code show how to retrieve the messages in the main part of the script
## def on_message(client1, userdata, message): while len(messages)>0: print(messages.pop(0)) while not q.empty(): message = q.get() print("queue: ",message)
If I run the example script you see the following:
The demo script for processing messages in the callback is here:
Common Questions and Answers
Q- My callback is not being triggered?
A- Have you assigned the function to the Callback? Are you running a loop for that client?
Q-My callback returns a value but I don’t see it?
A- Because of the asynchronous nature of callbacks it doesn’t make sense to return a value from them.
Video- Python MQTT Client The Loop and Callbacks Explained
Script used in this Tutorial