I'm trying to write a script that saves mqtt data and sends it to influxDB. The issue I'm having is that the callback function of the mqtt-paho module keeps giving the error:
AttributeError: 'Client' object has no attribute 'write_api'
. I think this is because of the self
in the internal 'Client' class of the mqtt-paho. My full script can be found below:
# Imported modules
# standard time module
from datetime import datetime
import time
# InfluxDB specific modules
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
#MQTT paho specific modules
import paho.mqtt.client as mqtt
class data_handler(): # Default namespaces are just for all the ESPs.
def __init__(self, namespace_list=["ESP01","ESP02","ESP03","ESP04","ESP05","ESP06","ESP07","ESP08"]):
# initialize influxdb client and define access token and data bucket
token = "XXXXXXXXXX" # robotlab's token
self.org = "Home"
self.bucket = "HomeSensors"
self.flux_client = InfluxDBClient(url="http://localhost:8086", token=token)
self.write_api = self.flux_client.write_api(write_options=SYNCHRONOUS)
# Initialize and establish connection to MQTT broker
broker_address="XXX.XXX.XXX.XXX"
self.mqtt_client = mqtt.Client("influx_client") #create new instance
self.mqtt_client.on_message=data_handler.mqtt_message #attach function to callback
self.mqtt_client.connect(broker_address) #connect to broker
# Define list of namespaces
self.namespace_list = namespace_list
print(self.namespace_list)
def mqtt_message(self, client, message):
print("message received " ,str(message.payload.decode("utf-8")))
print("message topic=",message.topic)
print("message qos=",message.qos)
print("message retain flag=",message.retain)
sequence = [message.topic, message.payload.decode("utf-8")]
self.write_api.write(self.bucket, self.org, sequence)
def mqtt_listener(self):
for namespace in self.namespace_list:
self.mqtt_client.loop_start() #start the loop
print("Subscribing to topics!")
message = namespace "/#"
self.mqtt_client.subscribe(message, 0)
time.sleep(4) # wait
self.mqtt_client.loop_stop() #stop the loop
def main():
influxHandler = data_handler(["ESP07"])
influxHandler.mqtt_listener()
if __name__ == '__main__':
main()
The code works fine until I add self.someVariable
in the callback function. What would be a good way to solve this problem? I don't really want to be making global variables hence why I chose to use a class.
Thanks in advance!
CodePudding user response:
Dealing with self
when there are multiple classes involved can get confusing. The paho library calls on_message
as follows:
on_message(self, self._userdata, message)
So the first argument passed is the instance of Client
so what you are seeing is expected (in the absence of any classes).
If the callback is a method object (which appears to be your aim) "the instance object is passed as the first argument of the function". This means your function would take four arguments and the definition be:
mqtt_message(self, client, userdata, msg)
Based upon this you might expect your application to fail earlier than it is but lets look at how you are setting the callback:
self.mqtt_client.on_message=data_handler.mqtt_message
datahandler
is the class itself, not an instance of the class. This means you are effectively setting the callback to a static function (with no binding to any instance of the class - this answer might help). You need to change this to:
self.mqtt_client.on_message=self.mqtt_message
However this will not work as the method currently only takes three arguments; update the definition to:
def mqtt_message(self, client, userdata, msg)
with those changes I believe this will work (or at least you will find another issue :-) ).
An example might be a better way to explain this:
class mqtt_sim():
def __init__(self):
self._on_message = None
@property
def on_message(self):
return self._on_message
@on_message.setter
def on_message(self, func):
self._on_message = func
# This is what you are doing
class data_handler1(): # Default namespaces are just for all the ESPs.
def __init__(self):
self.mqtt = mqtt_sim()
self.mqtt.on_message = data_handler1.mqtt_message # xxxxx
def mqtt_message(self, client, message):
print("mqtt_message1", self, client, message)
# This is what you should be doing
class data_handler2(): # Default namespaces are just for all the ESPs.
def __init__(self):
self.mqtt = mqtt_sim()
self.mqtt.on_message = self.mqtt_message #attach function to callback
def mqtt_message(self, mqttself, client, message):
print("mqtt_message2", self, mqttself, client, message)
# Lets try using both of the above
d = data_handler1()
d.mqtt._on_message("self", "userdata", "message")
d = data_handler2()
d.mqtt._on_message("self", "userdata", "message")