i have a mqtt initializer class
class Initializer():
def __init__(self):
self.client = mqtt.Client(mqtt_server+str(int(time.time())))
self.client.username_pw_set(
username=mqtt_username, password=mqtt_password)
self.client.on_connect = self.on_connect
self.client.subscribe(
"topic")
self.client.connect(broker, mqtt_port)
self.client.loop_start()
inherited this class to another class
class publishdata(Initializer):
def __init__(self):
super().__init__()
self.client.on_message = self.on_message
self.client.on_subscribe = self.on_subscribe
def on_subscribe(self, client, userdata, mid, granted_qos):
print("Subscription started")
def on_message(self, client, userdata, message):
print("message.topic", message.payload)
def begin(self,topic,data):
self.client.publish(
topic, str(data))
publishData = PublishData()
publishData.begin(topic,data)
publish and subscribe works properly. but when i call publishedData .client.connect and client.loop_start in the Initializer class also runs.i dont want that to be excecuted on every publish call. is there any better way to do this
If you don't want to initialize a client every time that you create a new PublishData instance, then the code that does this initialization does not belong in the PublishData class. One possible solution is to create the client outside of the PublishData class and pass it in. This is called dependency injection. It looks something like this:
def start_client():
client = mqtt.Client(mqtt_server+str(int(time.time())))
client.username_pw_set(username=mqtt_username, password=mqtt_password)
client.on_connect = self.on_connect
client.subscribe("topic")
client.connect(broker, mqtt_port)
client.loop_start()
return client
client = start_client()
publish_data = PublishData(client)
publishData.begin(topic,data)
Related
I've my Mosquitto MQTT broker and I've created a simple Django APP that subscribes to the topic $SYS/broker/uptime like below
from django.apps import AppConfig
from threading import Thread
import paho.mqtt.client as mqtt
class MqttClient(Thread):
def __init__(self, broker, port, timeout, topics):
super(MqttClient, self).__init__()
self.client = mqtt.Client()
self.broker = broker
self.port = port
self.timeout = timeout
self.topics = topics
self.total_messages = 0
# run method override from Thread class
def run(self):
self.connect_to_broker()
def connect_to_broker(self):
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
self.client.connect(self.broker, self.port, self.timeout)
self.client.loop_forever()
# The callback for when a PUBLISH message is received from the server.
def on_message(self, client, userdata, msg):
self.total_messages = self.total_messages + 1
print(str(msg.payload) + "Total: {}".format(self.total_messages))
# The callback for when the client receives a CONNACK response from the server.
def on_connect(self, client, userdata, flags, rc):
# Subscribe to a list of topics using a lock to guarantee that a topic is only subscribed once
for topic in self.topics:
client.subscribe(topic)
class AppMqtteConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app_mqtt'
def ready(self):
MqttClient("localhost", 1883, 60, ["$SYS/broker/uptime"]).start()
For some reason, the print statement on the on_message callback got executed two times, at least from what I'm seeing from the console. See screenshot. I can't understand why
It turned out that in some instances, particularly in tests, the ready method could be called multiple times as suggested in the Documentation.
As explained on this answer the issue is resolved with an if os.environ.get('RUN_MAIN'): before the ready method
I have a very simple setup inspired by this question: Tornado - Listen to multiple clients simultaneously over websockets
Essentially, I have one Websocket Handler that may connect to many websocket clients. Then I have another websocket handler 'DataHandler' that will broadcast a message everytime it receives a message.
So I made a global list of TestHandler instances and use it to broadcast messages to all the instances
ws_clients = []
class TestHandler(tornado.websocket.WebSocketHandler):
def open(self):
print('open test!')
ws_clients.append(self)
self.random_number = random.randint(0, 101)
def on_message(self, message):
print(message)
print('received', message, self, self.random_number)
self.write_message('Message received')
def on_close(self):
print('closed')
class DataHandler(tornado.websocket.WebSocketHandler):
def open(self):
print('data open!')
def on_message(self, message):
for c in ws_clients:
c.write_message('hello!')
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/test_service/", TestHandler),
(r"/data/", DataHandler),
(r"/", httpHandler)
]
tornado.web.Application.__init__(self, handlers)
ws_app = Application()
ws_app.listen(8000)
tornado.ioloop.IOLoop.instance().start()
TestHandler can receive messages fine through the address ws://127.0.0.1/test_service/ and DataHandler can receive messages fine through the address ws://127.0.0.1/data/ but whenever I loop through ws_clients, I never receive any messages on TestHandler.
Am I doing something wrong?
Here's what I'd do - I'd create a new method on TestHandler which will serve
one single purpose - take a message and send it to all the connected clients.
Before going into the code, I'd like to point out that it seems (conventionally) better to keep ws_clients inside the class instead of a global object. And use a set instead of a list.
class TestHandler(...):
ws_clients = set() # use set instead of list to avoid duplicate connections
def open(self):
self.ws_clients.add(self)
#classmethod
def broadcast(cls, message):
"""Takes a message and sends to all connected clients"""
for client in cls.ws_clients:
# here you can calculate `var` depending on each client
client.write_message(message)
def on_close(self):
# remove the client from `ws_clients`
self.ws_client.remove(self)
# then you can call TestHandler.broadcast
# from anywhere in your code
# example:
class DataHandler(...):
...
def on_message(self, message):
# pass the message to TestHandler
# to send out to connected clients
TestHandler.broadcast(message)
I tried creating instance variable but it doesn't seem to be working. I want my data_list value in some other script with providing any argument.
class MqttData:
def on_connect(self,client, userdata, flags, rc):
print("Connected with result code "+str(rc))
# Subscribing in on_connect() means that if we lose the connection and
# reconnect then subscriptions will be renewed.
client.subscribe(TOPIC)
# The callback for when a PUBLISH message is received from the server.
def on_message(self,client, userdata, msg):
global count
data=msg.payload.decode('ascii')
self.data_list=data.split('\n')
print(self.data_list)
#updatesheet.update_spreadsheet(data_list)
def justafunction(self):
return self.data_list
Add at the top of your class:
def __init__(self):
self.data_list = None # initialises to None at class instantiation
Then further down check whether it has been assigned before using it:
...
if self.data_list:
do_something()
I'm new to tornado.I am trying to build a chat server proxy with tornado,I got the message from the web client,normally it just need to send it back,however,i need to send those message to another server first,here comes the problem,it costs a lot of time to wait the other server response,i need to make it no-blocking,but when i use the anonymous methods of the tornado,it doesn't work at all,help me,thank you very much!
That's the part of my pseudo codeļ¼
class ClientWSConnectienter(websocket.WebSocketHandler):
_thread_pool = ThreadPoolExecutor(20)
def initialize(self, room_handler):
#chat room initiate
self.__rh = room_handler
#run_on_executor(executor='_thread_pool')
def worker(self,msg):
#send the msg to another server
pmessage=send_msg_to_server(msg)
return pmessage
#tornado.web.asynchronous
#tornado.gen.coroutine
def on_message(self, message):
#this will blocking for too much time,and I want make it no-blocking
pmessage=yeild worker(msg)
#send the recive pmessage to others client
room.write_message(pmessage)
self.finish()
obviously,it doesn't work,I got something like this:
error:websocket cannot use this method
So,what should I do? thanks a lot
But after I reedit my code,it still blocks in the task part.I don't know why,this is still part of my code
Re_edit:
class ClientWSConnection(websocket.WebSocketHandler):
def initialize(self, room_handler):
self.queue = tornado.queues.Queue()
def open(self, client_id):
IOLoop.current().spawn_callback(self.loop)
def on_message(self, message):
self.queue.put(msg)
def on_close(self):
self.queue.put(None)
#coroutine
def loop(self):
while 1:
msg=yield self.queue.get()
if msg is None:
return
msg=yield self.worker(msg)
pmessage = msg
room.write_message(pmessage)
#coroutine
def worker(self,msg):
#need to send the other server,blocking here
time.sleep(10)
raise Return(msg)
I think that error message is coming from your call to finish(), which is not meaningful for websockets (did you mean close()?). (Also, there's no need to use both #asynchronous and #coroutine; #coroutine alone is sufficient)
But there's a bigger problem: Remember that when overriding methods defined in a superclass, you can only make them a coroutine if the documentation says you can (because coroutines are called differently from regular methods). WebSocketHandler.on_message does not currently (as of Tornado 4.3) support coroutines.
So you need to use a queue to hand this off to another task. Something like this:
class MyHandler(WebSocketHandler):
def initialize(self):
self.queue = tornado.queues.Queue()
def on_open(self):
IOLoop.current().spawn_callback(self.loop)
def one_message(self, msg):
self.queue.put(msg)
def on_connection_close(self):
self.queue.put(None)
#coroutine
def loop(self):
while True:
msg = yield self.queue.get()
if msg is None:
return
pmessage = yield self.worker(msg)
self.write_message(pmessage)
Firstly, there is an IO class, which on __init__ is passed the asyncio loop object (io = IO(loop)), created earlier in the main class. IO class then at some point initializes the Socket class by doing self.socket = Socket(self), so that the socket object has a backwards access. Later, the Socket class initializes Websocket class which is a subclass of Transport
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket):
self.socket = socket
def open(self):
url = self.prepareUrl()
factory = WebSocketClientFactory(url, debug = False)
factory.protocol = Protocol
websocket = self.socket.loop.create_connection(factory, host=self.socket.io.options.host, port=self.socket.options.port)
self.socket.io.loop.run_until_complete(websocket)
def onOpen(self):
print('print me please!')
So, socket object calls self.transport.open() (where self.transport = Websocket(self)), which creates autobahn factory, creates asyncio connection by doing self.socket.loop.create_connection() and then adds the coro future to the loop by executing run_until_complete().
Now, this is where the problem starts:
autobahn factory requires a class, which must inherit from autobahn.asyncio.websocket.WebSocketClientProtocol
My class Protocol(WebSocketClientProtocol) has the usual:
class Protocol(WebSocketClientProtocol):
#asyncio.coroutine
def onOpen(self):
print('socket opened!')
This works perfectly fine, the print('socket opened!') does print the string and my server also says the connection is open.
The question:
from the Protocol() class, when the onOpen() callback is called by autobahn, how can I make this method call the transport.onOpen() method and do print('print me please!')?
OK, so I fixed it! Easily done with PyDispatch module.
Here is my solution:
import asyncio
from pydispatch import dispatcher
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory
from ..transport import Transport
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket):
self.socket = socket
def open(self):
url = self.prepareUrl()
factory = WebSocketClientFactory(url, debug = False)
factory.protocol = Protocol
websocket = self.socket.loop.create_connection(factory, host=self.socket.io.options.host, port=self.socket.options.port)
dispatcher.connect(self.onOpen, signal='open', sender=dispatcher.Anonymous)
self.socket.io.loop.run_until_complete(websocket)
def onOpen(self):
print('print me please!')
class Protocol(WebSocketClientProtocol):
#asyncio.coroutine
def onOpen(self):
dispatcher.send(signal='open')
UPDATE
I've got another, IMO better solution to this. This one is not using PyDispatch. Since there is a callback when an asyncio task finishes, which returns the user-defined protocol object (which inherits from WebSocketClientProtocol), we can use that to link the two objects together:
import asyncio
from autobahn.asyncio.websocket import WebSocketClientProtocol, WebSocketClientFactory
from ..transport import Transport
class Protocol(WebSocketClientProtocol):
def __init__(self):
self.ws = None
super().__init__()
#asyncio.coroutine
def onConnect(self, response):
pass # connect handeled when SocketIO 'connect' packet is received
#asyncio.coroutine
def onOpen(self):
self.ws.onOpen()
#asyncio.coroutine
def onMessage(self, payload, isBinary):
self.ws.onMessage(payload=payload, isBinary=isBinary)
#asyncio.coroutine
def onClose(self, wasClean, code, reason):
if not wasClean:
self.ws.onError(code=code, reason=reason)
self.ws.onClose()
class Websocket(Transport):
name = 'websocket'
def __init__(self, socket, **kwargs):
super().__init__(socket)
loop = kwargs.pop('loop', None)
self.loop = loop or asyncio.get_event_loop()
self.transport = None
self.protocol = None
self.ready = True
def open(self):
url = self.prepareUrl()
if bool(self.socket.options.query):
url = '{0}?{1}'.format(url, self.socket.options.query)
factory = WebSocketClientFactory(url=url, headers=self.socket.options.headers)
factory.protocol = Protocol
coro = self.loop.create_connection(factory, host=self.socket.options.host, port=self.socket.options.port, ssl=self.socket.options.secure)
task = self.loop.create_task(coro)
task.add_done_callback(self.onWebSocketInit)
def onWebSocketInit(self, future):
try:
self.transport, self.protocol = future.result()
self.protocol.ws = self
except Exception:
self.onClose()
def send(self, data):
self.protocol.sendMessage(payload=data.encode('utf-8'), isBinary=False)
return self
def close(self):
if self.isOpen:
self.protocol.sendClose()
return self
def onOpen(self):
super().onOpen()
self.socket.setBuffer(False)
def onMessage(self, payload, isBinary):
if not isBinary:
self.onData(payload.decode('utf-8'))
else:
self.onError('Message arrived in binary')
def onClose(self):
super().onClose()
self.socket.setBuffer(True)
def onError(self, code, reason):
self.socket.onError(reason)