Python flask mqtt socketio and subscribing on startup - python-3.x

When starting a flask, mqtt and socketio app how exactly can I subscribe to topics when the app starts but before the browser connects for the first time?
I had assumed I could use before_first_request but that only gets called on first request, also assume I could use mqtt.on_connect but that is never called when using socket io and if I subscribe before starting the app in __main__ then I get two threads subscribed rather than one.
#!/usr/bin/env python3
import json
from flask import Flask, render_template
from flask_mqtt import Mqtt
from flask_socketio import SocketIO
from flask_bootstrap import Bootstrap
# Flask App
app = Flask(__name__)
app.config['MQTT_BROKER_URL'] = '192.168.109.135'
print('Configured MQTT IP Address: ' + app.config['MQTT_BROKER_URL'])
mqtt = Mqtt(app)
socketio = SocketIO(app)
#app.route("/")
def roots():
return render_template('index.html')
#app.route('/mqtt')
def mqtt_index():
return render_template('mqtt.html')
#socketio.on('subscribe')
def handle_subscribe(json_str):
print('Subscribe ' + json_str)
#socketio.on('unsubscribe_all')
def handle_unsubscribe_all():
print('Socket IO unsubscribe all')
mqtt.unsubscribe_all()
#socketio.on('connect')
def handle_connect():
print('Socket IO Connected')
#socketio.on('discconnect')
def handle_connect():
print('Socket IO Discconnect')
#mqtt.on_connect()
def handle_mqtt_connect():
print('MQTT Connected')
#mqtt.on_message()
def handle_mqtt_message(client, userdata, message):
print('MQTT Message')
data = dict(
topic=message.topic,
payload=message.payload.decode(),
qos=message.qos,
)
print(mqttresponse)
socketio.emit('mqtt_message', data=data)
#mqtt.on_log()
def handle_logging(client, userdata, level, buf):
print('MQTT log', level, buf)
pass
#app.before_first_request
def before_first_request():
print("before_first_request")
mqtt.subscribe('homeassistant/+/+/set', 0)
if __name__ == "__main__":
# Main http web server for firmware downloading and the main frontend.
socketio.run(app, host='0.0.0.0', port='6080', use_reloader=True)
Any ideas where the mqtt.subscribe should go so it subscribes to the topics I want before the first connect to the webserver?

I found there is already a related issue which is the on_connect callback doesn't get called, and a question on here too. So this is a duplicate.
Flask MQTT on_connect is never called when used with SocketIO
https://github.com/stlehmann/Flask-MQTT/issues/82

Related

Connect Python websocket library with Plotly Dash

I haven't been able to find an example that connects Python Websocket client to Plotly Dash and am wondering what the best way to design this it. My Websocket server that listens to a stock trade stream implementation looks like this:
class StockSocket:
def __init__(self):
self.underlying_symbol = underlying_symbol
self.last_underlying_price = ''
def on_message(self, ws, message):
m = json.loads(message)
self.last_underlying_price = str(m['last'])
# I want to send this message to client here
def on_error(self, ws, error):
print(error)
def on_close(self, ws, close_status_code, close_msg):
print("### closed ###")
def on_open(self, ws):
payload = json.dumps({"symbol": self.underlying_symbol, "sessionid": self.sessionId})
ws.send(payload)
def run_forever(self):
websocket.WebSocketApp("<market-feed-url>",on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close).run_forever()
def runWebsocket(sessionId, underlying_symbol):
ws = StockSocket(sessionId, underlying_symbol)
ws.run_forever()
I plan on running this via Python Flask server and then have Dash Websocket listen in, but am not quite sure about the implementation.
For client side code I was thinking something like this:
import dash_core_components as dcc
import dash_html_components as html
import dash_extensions as de
from dash import Dash
from dash.dependencies import Input, Output
# Create example app.
app = Dash(prevent_initial_callbacks=True)
app.layout = html.Div([
dcc.Input(
id="input", autoComplete="off"
),
html.Div(id="msg"),
de.WebSocket(url="ws://localhost:8000/", id="ws")
])
# Send input value using websocket.
send = "function(value){return value;}"
app.clientside_callback(
send,
Output("ws", "send"),
[Input("input", "value")],
)
# Update div using websocket.
receive = "function(msg){console.log(msg.data); return \"Response from websocket: \" + msg.data;}"
app.clientside_callback(
receive,
Output("msg", "children"),
[Input("ws", "message")]
)
if __name__ == '__main__':
app.run_server(host='0.0.0.0', port=8000, debug=True, use_reloader=False)
My two main questions are:
How do I send message from server to client using Python Websocket library (documentation is sparse)
How does client poll the server?
Thanks!
The generic websocket in python is this:
import asyncio
from websockets import connect
async def hello(uri):
async with connect(uri) as websocket:
await websocket.send("Hello world!")
await websocket.recv()
asyncio.run(hello("ws://localhost:8765"))
But you can get everything that you need from here:
https://pypi.org/project/websockets/
and then here:
https://websockets.readthedocs.io/en/stable/intro/index.html

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.

Flask server using asynchronous Rpc client only answer 1 request out of two

I'm trying to implement an async RPC client within a Flask server.
The idea is that each request spawn a thread with an uuid, and each request is going to wait until there is a response in the RpcClient queue attribute object with the correct uuid.
The problem is that one request out of two fails. I think that might be a problem with multi-threading, but I don't see where it comes from.
Bug can be seen here.
Using debug print, it seems that the message with the correct uuid is received in the _on_response callback and update the queue attribute in this instance correctly, but the queue attribute within the /rpc_call/<payload> endpoint doesn't synchronize (so queue[uuid] has a value of response in the RpcClient callback but still None in the scope of the endpoint).
My code:
from flask import Flask, jsonif
from gevent.pywsgi import WSGIServer
import sys
import os
import pika
import uuid
import time
import threading
class RpcClient(object):
"""Asynchronous Rpc client."""
internal_lock = threading.Lock()
queue = {}
def __init__(self):
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(host='rabbitmq'))
self.channel = self.connection.channel()
self.channel.basic_qos(prefetch_count=1)
self.channel.exchange_declare(exchange='kaldi_expe', exchange_type='topic')
# Create all the queue and bind them to the corresponding routing key
self.channel.queue_declare('request', durable=True)
result = self.channel.queue_declare('answer', durable=True)
self.channel.queue_bind(exchange='kaldi_expe', queue='request', routing_key='kaldi_expe.web.request')
self.channel.queue_bind(exchange='kaldi_expe', queue='answer', routing_key='kaldi_expe.kaldi.answer')
self.callback_queue = result.method.queue
.
thread = threading.Thread(target=self._process_data_events)
thread.setDaemon(True)
thread.start()
def _process_data_events(self):
self.channel.basic_consume(self.callback_queue, self._on_response, auto_ack=True)
while True:
with self.internal_lock:
self.connection.process_data_events()
time.sleep(0.1)
def _on_response(self, ch, method, props, body):
"""On response we simply store the result in a local dictionary."""
self.queue[props.correlation_id] = body
def send_request(self, payload):
corr_id = str(uuid.uuid4())
self.queue[corr_id] = None
with self.internal_lock:
self.channel.basic_publish(exchange='kaldi_expe',
routing_key="kaldi_expe.web.request",
properties=pika.BasicProperties(
reply_to=self.callback_queue,
correlation_id=corr_id,
),
body=payload)
return corr_id
def flask_app():
app = Flask("kaldi")
#app.route('/', methods=['GET'])
def server_is_up():
return 'server is up', 200
#app.route('/rpc_call/<payload>')
def rpc_call(payload):
"""Simple Flask implementation for making asynchronous Rpc calls. """
corr_id = app.config['RPCclient'].send_request(payload)
while app.config['RPCclient'].queue[corr_id] is None:
#print("queue server: " + str(app.config['RPCclient'].queue))
time.sleep(0.1)
return app.config['RPCclient'].queue[corr_id]
if __name__ == '__main__':
while True:
try:
rpcClient = RpcClient()
app = flask_app()
app.config['RPCclient'] = rpcClient
print("Rabbit MQ is connected, starting server", file=sys.stderr)
app.run(debug=True, threaded=True, host='0.0.0.0')
except pika.exceptions.AMQPConnectionError as e:
print("Waiting for RabbitMq startup" + str(e), file=sys.stderr)
time.sleep(1)
except Exception as e:
worker.log.error(e)
exit(e)
I found where the bug came from:
Thedebug=True of the line app.run(debug=True, threaded=True, host='0.0.0.0') restart the server at the beginning.
The whole script is then restarted from the beginning. Because of it, another rpcClient is initialized and consume from the same queue. Problem is that the previous thread is also running. This cause two rpcClient to consume from the same thread, with one that is virtually useless.

Flask-socketio doesn't recieve message from client

I'm trying to write a basic Socket.io program where the python client (python-socketio[asyncio_client] 4.6.0) emits a single string message to the flask server (with Flask-SocketIO 4.3.1 and eventlet).
The client appears to connect and send the message properly, but there is no output seen at the Flask server.
Server code:
from flask import Flask
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
#socketio.on('connect')
def test_connect():
print('connected')
#socketio.on('disconnect')
def test_disconnect():
print('Client disconnected')
#socketio.on('message')
def handle_message(msg):
print('Recieved',msg)
#socketio.on('json')
def handle_json(json):
print(str(json))
if __name__ == '__main__':
socketio.run(app,debug=True)
Client code:
import asyncio
import socketio
sio = socketio.AsyncClient()
#sio.event
def connect():
print('connection established')
#sio.event
def disconnect():
print('disconnected from server')
async def main():
await sio.connect('http://localhost:5000')
await sio.emit('message',data='detection')
print('message sent')
await sio.disconnect()
if __name__ == '__main__':
asyncio.run(main())
Server output:
PS C:\Users\daksh\sih\sihPython> python .\test_socketio.py
* Restarting with stat
* Debugger is active!
* Debugger PIN: 101-561-255
(16664) wsgi starting up on http://127.0.0.1:5000
(16664) accepted ('127.0.0.1', 59497)
connected
127.0.0.1 - - [23/Jul/2020 20:38:42] "GET /socket.io/?transport=polling&EIO=3&t=1595516920.71801 HTTP/1.1" 200 367 0.004934
Client disconnected
127.0.0.1 - - [23/Jul/2020 20:38:42] "GET /socket.io/?transport=websocket&EIO=3&sid=88790300120f4b899e019ae7cc16ee87&t=1595516922.7757218 HTTP/1.1" 200 0 0.010027
Client output:
PS C:\Users\daksh\sih\sihPython> python .\socketio-client.py
connection established
message sent
The print statement from handle_message() is missing in the server output.
I've gone through multiple tutorials online, and I've tried it with and without namespaces. Have not been able to figure out what's wrong.
Any help is appreciated.
(I'm using Python 3.8.3 on Windows 10)
UPDATE: It works if I change the client code to use socketio.Client() instead of AsyncClient(), however I need the client to connect using AsyncClient.
The problem is that your async client is obviously asynchronous, you can't just send and exit because you don't give the background tasks that support the Socket.IO protocol time to do their thing.
Here is a more robust version of the client that lets the event go through before exiting:
import asyncio
import socketio
sio = socketio.AsyncClient()
#sio.event
async def connect():
print('connection established')
await sio.emit('message',data='detection', callback=done)
print('message sent')
#sio.event
def disconnect():
print('disconnected from server')
async def done():
await sio.disconnect()
async def main():
await sio.connect('http://localhost:5000')
await sio.wait()
if __name__ == '__main__':
asyncio.run(main())
The trick here is to use a callback on the emit. When the callback is invoked you are sure the message was delivered, so at that point it is safe to disconnect.

How to run Flask SocketIO in a thread?

I'm trying to run Flask SocketIO in thread using Python 3 and I cannot get it to work.
My code will not continue in my while loop.
How can I run it in thread?
import threading
from flask import Flask, render_template, request, redirect, url_for
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
#turn the flask app into a socketio app
socketio = SocketIO(app)
#app.route("/")
def index():
return render_template('index.html', **templateData)
if __name__ == "__main__":
threading.Thread(target=socketio.run(app),kwargs=dict(debug=False, use_reloader=False,host='0.0.0.0', port=5000)).start()
sleep(2)
while True:
try:
Print("Hello I'm in a while loop")
except KeyboardInterrupt:
sys.exit()
you should pass socketio.run as target, and app as argument
threading.Thread(
target=socketio.run,
args=(app,),
kwargs=dict(debug=False, use_reloader=False,host='0.0.0.0', port=5000)
).start()
also seems like you forget to import sleep from time
from time import sleep
and also please notice that templateData is not defined in code

Resources