how to communicate between regular MQTT and tornado websocket? - python-3.x

Is there any way to make a mqtt client send messages to tornado websocket?
Using this code, tornado works. While from MQTT no message passes.
import os.path
import tornado.httpserver
import tornado.web
import tornado.ioloop
import tornado.options
import tornado.httpclient
import tornado.websocket
import json
import random
from paho.mqtt import client as mqtt_client
broker = 'xxxxxxx'
port = 1883
topic = "xxx/xxxx"
client_id = f'xxxx-{random.randint(0, 100)}'
clients = []
def connect_mqtt() -> mqtt_client:
def on_connect(clientMQTT, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT Broker!")
else:
print("Failed to connect, return code %d\n", rc)
clientMQTT = mqtt_client.Client(client_id)
# clientMQTT.username_pw_set(username, password)
clientMQTT.on_connect = on_connect
clientMQTT.connect(broker, port)
return clientMQTT
def subscribe(clientMQTT: mqtt_client):
def on_message(clientMQTT, userdata, msg):
print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")
send_to_all_clients(msg.payload.decode())
clientMQTT.subscribe(topic)
clientMQTT.on_message = on_message
def runMQTT():
clientMQTT = connect_mqtt()
subscribe(clientMQTT)
clientMQTT.loop_start()
# clientMQTT.loop_forever()
def send_to_all_clients(message):
for clientws in clients:
clientws.write_message(message)
class SocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print("new client")
clients.append(self)
def on_close(self):
clients.remove(self)
print("removing client")
def on_message(self, message):
pass
# for client in utility.clients:
# if client != self:
# client.write_message(msg)
if __name__ == '__main__':
app = tornado.web.Application(
handlers = [
(r"/monitor", SocketHandler)
],
debug = True,
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "static")
)
runMQTT()
app.listen(8200)
tornado.ioloop.IOLoop.instance().start()
I hope someone can give me some help.
Thank you all
If I try to listen on mqtt, with no tornado, it works.
I would like to be able to send messages from mqtt to browsers via tornado websocket.

Related

How to send ros2 messages from a websocket server to connected clients in tornado

I have a ros2 publisher script that sends custom messages from ros2 nodes. What I need to do is to have a subscriber (which is also my websocket server) to listen to the message that the pulisher sends then convert it to a dictionary and send it as a json from the websocket server to a connected websocket client. I have already checked the rosbridge repo but I could not make it work. It doesn't have enough documentation and I am new to ros.
I need something like this:
import rclpy
import sys
from rclpy.node import Node
import tornado.ioloop
import tornado.httpserver
import tornado.web
import threading
from custom.msg import CustomMsg
from .convert import message_to_ordereddict
wss = []
class wsHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'Online'
if self not in wss:
wss.append(self)
def on_close(self):
print 'Offline'
if self in wss:
wss.remove(self)
def wsSend(message):
for ws in wss:
ws.write_message(message)
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(CustomMsg, 'topic', self.CustomMsg_callback, 10)
self.subscription # prevent unused variable warning
def CustomMsg_callback(self, msg):
ws_message = message_to_ordereddict(msg)
wsSend(ws_message)
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(tornado.web.Application(wsHandler))
http_server.listen(8888)
main_loop = tornado.ioloop.IOLoop.instance()
# Start main loop
main_loop.start()
so the callback function in MinimalSubscriber class, receives the ros message, converts it to dictionary and sends it to websocket client. I am a bit confused how to make these two threads (ros and websocket) to communicate with each other.
So I think I got a bit confused myself going through the threading. So I changed my approach and made it work using the tornado periodic callback and the spin_once function of rclpy as the callback function. I would post my solution as it might help some people who has the same issue.
import queue
import rclpy
from rclpy.node import Node
import tornado.ioloop
import tornado.httpserver
import tornado.web
from custom.msg import CustomMsg
from .convert import message_to_ordereddict
wss = []
class wsHandler(tornado.websocket.WebSocketHandler):
#classmethod
def route_urls(cls):
return [(r'/',cls, {}),]
def open(self):
print 'Online'
if self not in wss:
wss.append(self)
def on_close(self):
print 'Offline'
if self in wss:
wss.remove(self)
def make_app():
myWebHandler = wsHandler.route_urls()
return tornado.web.Application(myWebHandler)
message_queue = queue.Queue
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(CustomMsg, 'topic', self.CustomMsg_callback, 10)
self.subscription # prevent unused variable warning
def CustomMsg_callback(self, msg):
msg_dict = message_to_ordereddict(msg)
msg_queue.put(msg_dict)
if __name__ == "__main__":
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
def send_ros_to_clients():
rclpy.spin_once(minimal_subscriber)
my_msg = msg_queue.get()
for client in ws_clients:
client.write_message(my_msg)
app = make_app()
server = tornado.httpserver.HTTPServer(app)
server.listen(8888)
tornado.ioloop.PeriodicCallback(send_ros_to_clients, 1).start()
tornado.ioloop.IOLoop.current().start()
minimal_subscriber.destroy_node()
rclpy.shutdown()
I also implemented the wsSend function into the send_ros_to_clients function. Some might say that using a global queue is not the best practice but I could not come up with another solution. I would appreciate any suggestions or corrections on my solution.

How to reconnect to a websocket when connection fails in python

Hi I created async websocket client which I can receive and send messages asynchronously.
Here is the clint class that I use.
import websockets
import asyncio
from models import Heartbeat
from model_helper import ModelHelper
from json_helper import JSONHelper
from os.path import dirname, abspath
import logging
import time
waiting_time = 0.5
class WebSocketClient():
def __init__(self, websocket_queue, configs):
self.Heartbeat = Heartbeat().message
self.websocket_queue = websocket_queue
self.configs = configs
def get_connection_string(self):
server_url = self.configs.URL + ":" + str(self.configs.Port) + self.configs.Root
return server_url
async def connect(self):
try:
server_url = self.get_connection_string()
self.connection = await websockets.client.connect(server_url)
if self.connection.open:
print("Connection stablished. Client correcly connected")
# Send greeting
await self.connection.send(self.Heartbeat)
return self.connection
else:
print("Can not connect")
except ConnectionRefusedError as err:
print("Connection Error: {}".format(err))
async def send_message_to_socket(self, connection):
while True:
message = self.websocket_queue.get_send_queue()
try:
if message is not None:
message_ = ModelHelper.to_outgoing_request_model(message)
await connection.send(message_)
else:
await asyncio.sleep(waiting_time)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed')
self.connect()
async def receive_message_from_socket(self, connection):
while True:
try:
message = await connection.recv()
obj = JSONHelper.toObject(message)
print("Received object from websocket: {}".format(obj))
#If a websocket entry has SendMessage in its Action property
#Consider it as an sms content to be sent.
if(obj.Action == "SendMessage"):
self.websocket_queue.add_received_queue(obj)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed1')
async def send_heartbeat_to_socket(self, connection):
while True:
#print("Heartbeat loop\n")
try:
await connection.send(self.Heartbeat)
await asyncio.sleep(3)
except websockets.exceptions.ConnectionClosed:
print('Connection with server closed2')
And this is the code where I listen for websocket messages:
def listen_websocket_routine(sms_queue, websocket_queue):
while True:
time.sleep(waiting_time)
#Check Websocket queue for new messages
message = websocket_queue.get_received_queue()
# If Incoming websocket JSON has SendMessage string in its Action attribute transform it to request_model
if message is not None and send_sms.Action == "SendMessage":
# Transform it to outgoing_sms_model
reset = ModelHelper.to_outgoing_sms_model(message.Data)
# Add To send_queue of sms
sms_queue.add_send_queue(reset)
finally How I initiate them with asyncio and threading.
client = WebSocketClient(websocket_queue, configs)
loop = asyncio.get_event_loop()
connection = loop.run_until_complete(client.connect())
task1 = asyncio.ensure_future(client.receive_message_from_socket(connection))
task2 = asyncio.ensure_future(client.send_heartbeat_to_socket(connection))
task3 = asyncio.ensure_future(client.send_message_to_socket(connection))
listen_websocket_thread = threading.Thread(target=listen_websocket_routine, args=(sms_queue, websocket_queue))
listen_websocket_thread.start()
loop.run_forever()
So my question is, whenever connetion breaks, I need to re establish the connection. But I am not sure where should I do that. Should it be before everytime I try to send message or receive or should I do that in more general approach?
Since update 10.0 you could do it with an async iterator, like this(example from the official doc):
async for websocket in websockets.connect(...):
try:
...
except websockets.ConnectionClosed:
continue

Send data from an other class through ws after client connected

I want to send data through websockets as soon as a client is connected.
The Data is at an other place then the Websocket Handler. How can i get the data to the client ?
The server should hold the loop and the Handler. In the connector i connect to a tcp socket to get the data out of some hardware. I expect to have not more then 6 Websockets open once a time. The Data comes as a stream out of the TCP socket.
server.py
import os
from tornado import web, websocket
import asyncio
import connector
class StaticFileHandler(web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
def get(self):
self.render('index.html')
class WSHandler(websocket.WebSocketHandler):
def open(self):
print('new connection')
self.write_message("connected")
def on_message(self, message):
print('message received %s' % message)
self.write_message("pong")
def on_close(self):
print('connection closed')
public_root = 'web_src'
handlers = [
(r'/', StaticFileHandler),
(r'/ws', WSHandler),
]
settings = dict(
template_path = os.path.join(os.path.dirname(__file__), public_root),
static_path = os.path.join(os.path.dirname(__file__), public_root),
debug = True
)
app = web.Application(handlers, **settings)
sensorIP = "xxx.xxx.xxx.xxx"
if __name__ == "__main__":
app.listen(8888)
asyncio.ensure_future(connector.main_task(sensorIP))
asyncio.get_event_loop().run_forever()
connector.py
import yaml
import asyncio
class RAMReceiver:
def __init__(self, reader):
self.reader = reader
self.remote_data = None
self.initParams = None
async def work(self):
i = 0
while True:
data = await self.reader.readuntil(b"\0")
self.remote_data = yaml.load(data[:-1].decode("utf-8",
"backslashreplace"))
# here i want to emit some data
# send self.remote_data to websockets
if i == 0:
i += 1
self.initParams = self.remote_data
# here i want to emit some data after open event is
# triggered
# send self.initParams as soon as a client has connected
async def main_task(host):
tasks = []
(ram_reader,) = await asyncio.gather(asyncio.open_connection(host,
51000))
receiver = RAMReceiver(ram_reader[0])
tasks.append(receiver.work())
while True:
await asyncio.gather(*tasks)
You can use Tornado's add_callback function to call a method on your websocket handler to send the messages.
Here's an example:
1. Create an additional method on your websocket handler which will receive a message from connector.py and will send to connected clients:
# server.py
class WSHandler(websocket.WebSocketHandler):
# make it a classmethod so that
# it can be accessed directly
# from class without `self`
#classmethod
async def send_data(cls, data):
# write your code for sending data to client
2. Pass the currently running IOLoop and WSHandler.send_data to your connector.py:
# server.py
from tornado import ioloop
...
if __name__ == "__main__":
...
io_loop = ioloop.IOLoop.current() # current IOLoop
callback = WSHandler.send_data
# pass io_loop and callback to main_task
asyncio.ensure_future(connector.main_task(sensorIP, io_loop, callback))
...
3. Then modify main_task function in connector.py to receive io_loop and callback. Then pass io_loop and callback to RAMReceiver.
4. Finally, use io_loop.add_callback to call WSHandler.send_data:
class RAMReceiver:
def __init__(self, reader, io_loop, callback):
...
self.io_loop = io_loop
self.callback = callback
async def work(self):
...
data = "Some data"
self.io_loop.add_callback(self.callback, data)
...

problems usign paho.mqtt together with python-telegram-bot

Im struggling on get python-telegram-bot working together with paho-mqqt.
something code in updater.start_polling() seems to influence the mqqt thread, that was started thru client.loop_start().
after a a few publishes the mqqt client stops receiving messages.
Does anybody have idea whats going on ?
#!/usr/bin/python3
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
import telegram
import paho.mqtt.client as mqtt
import logging
import time
#...
def start(bot, update):
global client
update.message.reply_text('Hi!')
client.publish("/doorR/test","test")
def on_connect(self,client, userdata, rc):
print("Connected with result code " + str(rc))
self.subscribe("/doorR/#")
def on_message(client, userdata, msg):
print("Topic: ", msg.topic + "\nMessage: " + str(msg.payload))
if msg.topic == "/doorR/status":
bot = telegram.Bot(token=TOKEN)
bot.send_message(chat_id=MYCHATID, text="Funkmodul status geƤndert: " + str(msg.payload))
def main():
global client
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect("127.0.0.1", 1883, 60)
client.loop_start()
updater = Updater(TOKEN)
dp = updater.dispatcher
dp.add_handler(CommandHandler("start", start))
updater.start_polling()
time.sleep(10)
client.loop_stop()
if __name__ == '__main__':
main()

How do I receive new responses in the Websocket?

I have a client module:
#!/usr/bin/env python
# WS client example
import asyncio
import websockets
async def hello():
async with websockets.connect(
'ws://A.B.C.D:8765') as websocket:
name = input("What's your name? ")
await websocket.send(name)
greeting = await websocket.recv()
print(greeting)
asyncio.get_event_loop().run_until_complete(hello())
and the server module:
from __future__ import print_function
#!/usr/bin/env python
import asyncio
import datetime
import random
import websockets
import ast
from collections import defaultdict
import csv
import datetime
from itertools import chain
import json
import os
import operator
import sys
import pymongo
from pymongo import MongoClient
try:
client = MongoClient('localhost', 27017)
db = client["Bubble"]
except Exception as e:
print(e)
start_match = datetime.datetime.strptime(
"2018-07-01 18:00:00", '%Y-%m-%d %H:%M:%S')
collection = "CRODEN_R16"
async def hello(websocket, path):
entity_name = await websocket.recv()
print(entity_name)
while True:
file = open("set_start_match.txt", "r")
for line in file:
start_today = datetime.datetime.strptime(
line.split('.')[0], '%Y-%m-%d %H:%M:%S')
print(start_today)
now = datetime.datetime.utcnow()
diff = now - start_today
request_match = start_match + diff
print(diff)
for post in db[collection].find():
if "emotion" not in post.keys():
print("Ignored")
continue
if post["timeStamp"] > request_match:
if post["entity_name"] == entity_name:
print("Satisfied")
currDict = {}
currDict["entity"] = post["entity_name"]
currDict["emotion"] = max(
post["emotion"].items(), key=operator.itemgetter(1))[0]
currDict["profile_image"] = post["userProfile"]
currDict["tweet"] = post["tweet"]
currDict_json = json.dumps(currDict, default=str)
print(currDict["tweet"])
await websocket.send(currDict_json)
await asyncio.sleep(1)
del currDict
try:
start_server = websockets.serve(hello, '0.0.0.0', 8765)
print("Start entity server")
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
except Exception as e:
print(e)
Now, the issue is that I want to send name as an input only once and receive the output continuously.
When I wrote this in client:
while True:
greeting = await.websocket.recv()
print(greeting)
The same response is returned again and again. Even on the server side, where I am printing the rendered results from db, I am printing the same doc.
I am completely clueless as to what is the issue?
Note: I have tried to the run the once-run client module and there I was getting perfect results. It was just that I had to give the same input again and again. I want it to be automated.
To get data continuously someone has to send data continuously.
If someone sends data continuously then someone else has to get data continuously.
So both sides need loop.
client - it sends numbers continuously in loop.
#!/usr/bin/env python
import asyncio
import websockets
import time
async def hello():
async with websockets.connect(
'ws://localhost:8769') as websocket:
name = input("What's your name? ")
await websocket.send(name)
i = 0
while True:
print('send:', i)
await websocket.send(str(i))
time.sleep(2)
i += 1
try:
asyncio.get_event_loop().run_until_complete(hello())
except KeyboardInterrupt:
print('KeyboardInterrupt')
server - it receives numbers continuously in loop
import asyncio
import websockets
async def hello(websocket, path):
entity_name = await websocket.recv()
print('name:', entity_name)
while True:
data = await websocket.recv()
print('recv:', data)
try:
print("Start entity server")
start_server = websockets.serve(hello, '0.0.0.0', 8769)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
except KeyboardInterrupt: # keyboard need special except
print("KeyboardInterrupt")
start_server.ws_server.close() # solutin for [Errno 98]
except Exception as ex:
print(ex)

Resources