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
Related
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.
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.
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
I am trying to create a small service to respond to Envoy's rate limiting queries. I have compiled all the relevant protobuff files and the one relevant for the service I am trying to implement is here:
https://github.com/envoyproxy/envoy/blob/v1.17.1/api/envoy/service/ratelimit/v3/rls.proto
There is a service definition in there but inside of the "compiled" python file, all I see about it is this:
_RATELIMITSERVICE = _descriptor.ServiceDescriptor(
name='RateLimitService',
full_name='envoy.service.ratelimit.v3.RateLimitService',
file=DESCRIPTOR,
index=0,
serialized_options=None,
create_key=_descriptor._internal_create_key,
serialized_start=1531,
serialized_end=1663,
methods=[
_descriptor.MethodDescriptor(
name='ShouldRateLimit',
full_name='envoy.service.ratelimit.v3.RateLimitService.ShouldRateLimit',
index=0,
containing_service=None,
input_type=_RATELIMITREQUEST,
output_type=_RATELIMITRESPONSE,
serialized_options=None,
create_key=_descriptor._internal_create_key,
),
])
_sym_db.RegisterServiceDescriptor(_RATELIMITSERVICE)
DESCRIPTOR.services_by_name['RateLimitService'] = _RATELIMITSERVICE
here is my feeble attempt at implementing the service
import logging
import asyncio
import grpc
from envoy.service.ratelimit.v3.rls_pb2 import RateLimitResponse, RateLimitRequest
class RL:
def ShouldRateLimit(self, request):
result = RateLimitResponse()
def add_handler(servicer, server):
rpc_method_handlers = {
'ShouldRateLimit': grpc.unary_unary_rpc_method_handler(
RL.ShouldRateLimit,
request_deserializer=RateLimitRequest.FromString,
response_serializer=RateLimitResponse.SerializeToString,
)
}
generic_handler = grpc.method_handlers_generic_handler(
'envoy.service.ratelimit.v3.RateLimitService',
rpc_method_handlers
)
server.add_generic_rpc_handlers((generic_handler,))
async def serve():
server = grpc.aio.server()
add_handler(RL(), server)
listen_addr = '[::]:5051'
server.add_insecure_port(listen_addr)
logging.info(f'Starting server on {listen_addr}')
await server.start()
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
asyncio.run(serve())
How am I supposed to return (or even instantiate) a RateLimitResponse back to the caller ?
My program is to get parameter(URL) from web and in my tornado API, i want to accept them into one bye one. Now my code accepts a single tuple. My api want to fetch the 4 parameters(south-lat,south-long,east-lat,east-long) which comes from the web URL request. And i will implement them into get method of Tornado and find them in my database. I want to know how to accept them in tornado in seperately. This is my current working codes.
import socket
import tornado.web
from datetime import datetime
import pymongo
from pymongo import MongoClient
from pprint import pprint
import tornado.httpserver
import tornado.ioloop
import tornado.options
from tornado.options import define,options
#apicall/v1/geoloc.json?geoquery=True&southwest_lat=''&southwest_lng=''&northeast_lat=''&northeast_lng=''
#tornado port
port = 8088
host = "127.0.0.1"
#make a connection with mongodb
#client=MongoClient()
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("hello APIs")
#what this works is here
class VisualizationHandler(tornado.web.RequestHandler):
def get(self,*args):#how to accept the input from map?
self.open_args=args
#self.open_kwargs=kwargs
print(self.open_args)
print(type(self.open_args))
#self.write("Data is at the terminal")
#client = MongoClient("mongodb://localhost:27017")
#db=client.test
#docs=db.neighborhoods.findOne()
#if docs is None:
#print("Not found")
#else:
#print(docs)
#lat=self.get_argument('lat',True)
#long=self.get_argument('long',True)
#self.write(lat)
#self.write(long)
#var neighborhood = db.neighborhoods.findOne( { geometry: { $geoIntersects: { $geometry: { type: "Point", coordinates: [ slong, slat] } } } } )
def main():
application = tornado.web.Application([
(r"/", MainHandler),
(r"/geo/(.*)",VisualizationHandler),
])
try:
sockets = tornado.netutil.bind_sockets(port, address=host)
print("Socket binding successful to {0}:{1}".format(host, port))
except socket.error:
print("Socket binding failed to {0}:{1}".format(host, port))
return
try:
_id = tornado.process.fork_processes(0, max_restarts=3)
print("Process forked : {}".format(_id))
server = tornado.httpserver.HTTPServer(application)
server.add_sockets(sockets)
tornado.ioloop.IOLoop.current().start()
except KeyboardInterrupt: # stop with Ctrl+C from shell
print("Tornado is stopping")
if __name__ == "__main__":
print("Tornado is starting")
main()
get() method's args/kwargs are retrieved from the url (url regex's match groups, to be specific). Of course, you can create something like this to pass arguments to your function:
(r"/geo/(.*)/(.*)", VisualizationHandler)
or
(r"/geo/(?P<south_lat>.*)/(?P<south_long>.*)", VisualizationHandler)
and args/kwargs south_lat and south_long would be passed to your function from a URL like /geo/123/456, but it's inconvenient. I'd advise you to pass your arguments as URL params /geo?south_lat=123&south_long=456 and read them using get_argument().