How to run the Tornado event loop alongside a Kivy GUI? - python

My client application uses a Kivy GUI (Kivy has its own event loop) and connects to the server using the WebSocket protocol with Tornado (Tornado also has an event loop). That's why the connection part is asynchronous.
I want the user to interact with the UI while a Tornado client is running an infinite asynchronous loop of listening for server messages.
Here's some example code:
client_test.py
from tornado.ioloop import IOLoop
from tornado.websocket import websocket_connect
class RequestSender:
url = 'server url here (no scheme)'
async def _connect(self):
self.conn = await websocket_connect('wss://' + self.url, io_loop=self.ioloop)
self.ioloop.add_callback(self._listen)
async def _listen(self):
while True:
print(await self.conn.read_message())
def __init__(self):
self.ioloop = IOLoop.current()
self.ioloop.add_callback(self._connect)
def run(self):
self.ioloop.start()
GUI
from kivy.app import App
from kivy.uix.label import Label
from client_test import RequestSender
class TestApp(App):
def build(self):
RequestSender().run()
return Label(text = "hello")
TestApp().run()
Apparently, since the Tornado's event loop has started earlier, it has taken over the program flow and now no GUI window appears.
I execute the GUI file and the execution hangs after the RequestSender().run(), so build never returns.
Searching on this case provided little to no information, except for this Google Groups post. Kivy's documentation only mentions Twisted.
I tried putting the Kivy event loop into slave mode and running GUI updates from Tornado's event loop, but that didn't work because apparently a call EventLoop.idle() of Kivy's event loop isn't enough to keep the GUI application running.
What else could be done here?

I found this question trying to do the same thing, and opted for two separate processes instead; one Kivy GUI and one Tornado (Server, in my case). I have the two communicate using multiprocessing.connection as explained well in this SO answer
If you have large and complex data to pass between the two, perhaps this is less than ideal, but for simple messages it works well. You also have the advantage of running without the UI, and running the UI on a separate machine.

Related

Program gets stuck with tornado

I'm trying to make a Raspberry Pi send plain text to my phone over my local network, from where I plan to pick it up.
I tried the following "hello world"-like program from their official website, but I cannot get it to proceed after a point.
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Ugh, the world? Well.. hello, I guess")
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8881)
tornado.ioloop.IOLoop.instance().start()
# I cannot get this line to execute!!
print("Hi!!")
Experience: basics of Python, intermediate with Arduino C++, none in networking/web
You're trying to print to STDOUT after starting the event loop, so that print statement never sees the light of the day. Basically, you're creating a HTTP server at port 8881 that is constantly listening for requests. Whatever logic you wish the server to do needs to be in a callback, like MainHandler
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Ugh, the world? Well.. hello, I guess")
# All your SMS logic potentially goes here
self.write("Sent SMS to xyz...")
application = tornado.web.Application(
[
(r"/", MainHandler),
]
)
application.listen(8881)
tornado.ioloop.IOLoop.instance().start()
Then trigger the endpoint by making an HTTP call
curl <IP ADDRESS OF YOUR PI>:8881
This is because Tornado's IOLoop.start() method is a "blocking" call, which means that it doesn't return until some condition is met. This is why your code "gets stuck" on that line. The documentation for this method states:
Starts the I/O loop.
The loop will run until one of the callbacks calls stop(), which will
make the loop stop after the current event iteration completes.
Typically, a call to IOLoop.start() will be the last thing in your program. The exception would be if you want to stop your Tornado application and then proceed to do something else.
Here are two possible solutions to your problem, depending on what you want to accomplish:
call self.stop() from the handler. This will stop the Tornado application, IOLoop.start() will return, and then your print will execute.
call print("Hi!!") from your handler.

Wrap websockets asyncio with synchronous API

I'm using a python websockets library in order to create a websocket server. My goals is to export a synchronous API, as it's going to be used outside of python.
As such, I need, at least at the beginning, start() and stop() methods. So it seems right to create a websocket server class for that.
Main issue it that the way to create (and start) a server through the library is by awaiting, and so these methods should encouraged to be async, which I try to avoid.
The following code work perfectly when I run the main() function.
When runnig server = WebsocketServer(); server.start(1234) through ipython shell I can't seem to connect through a client code. What am I missing?
class WebsocketServer():
def __init__(self):
self._server = None
def start(self, port):
start_server = websockets.serve(self._handle_client,
host='localhost',
port=port)
self._server = asyncio.get_event_loop().run_until_complete(start_server)
def stop(self):
if self._server:
self._server.close()
self._server = None
async def _handle_client(self, client, path):
async for buffer in client:
await self._handle_buffer(client, buffer)
async def _handle_buffer(self, client, buffer):
print(buffer)
await client.send(buffer)
def main():
server = WebsocketServer()
server.start(1234)
asyncio.get_event_loop().run_forever()
The synchronous interface into the IO loop is via tasks. Scheduling methods return a future that can be synchronously waited on if needed. The run an event loop section of the docs features a combo for synchronous shutdown in the bottom.
When running inside an iPython shell, one option is to spawn a daemon background thread for the IO loop and register an atexit callback to synchronously shutdown the IO loop when the shell exits.
Another option is to "borrow" shell's thread once in a while for the IO tasks (only works for short tasks, of cause) using the UI Event Loop integration point described here describing how to borrow the shell thread for the IO.
You are missing the last line from you main function.
asyncio.get_event_loop().run_forever()
Nothing happens when the loop is not running. So, the server won't be running unless you run the loop.

How to write a Tornado listener correctly?

I wanted to write a server background listener to listen to dozens of clients sending commands to this background listener on TCP port 8888. Command format is "http://myip/?c="ClientCommand=A5"
Those clients could be sending commands at the same time or different time. Also, these clients will stay connected and sending few different commands until disconnect by client themselves. All the commands received by the background listener will be written to a MySQL DB.
After few days of study, I found Python + Tornado framework could be a good tool to do this. However, I have tried few days with examples from the web and I still don't know how to program this background listener.
Here is an example I found on a blog and it worked...How do I modify the code below to do the background listener? I know I need to use GET to get parameters from the URL. I have tried get(self.get), but I got error of invalid syntax...
Thanks for your help.
import tornado.ioloop
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, Tornado!")
if __name__ == "__main__":
application = tornado.web.Application([
(r"/", IndexHandler)
])
application.listen(8888)
tornado.ioloop.IOLoop.current().start()

how to write a multithread kivy game(on rasp Pi) that can listen to a port at the same time

I am writing a remote-control snake game on Raspberry Pi using kivy(output to the 7" display).
The socket is supposed to listen to the port while the game is running.
However it turns out that game loop and socketIO's wait loop can not run together. I tried multithreading but it didn't work as expected.
Code for socketIO:
from socketIO_client import SocketIO, BaseNamespace
class Namespace(BaseNamespace):
def on_connect(self):
print('[Connected]')
def on_message(self,packet):
print packet
self.get_data(packet)
def get_data(self, packet):
if(type(packet) is str):
matches = re.findall(PATTERN, packet)
if(matches[0][0]=='2'):
dataMatches = re.findall(DATAPATTERN, matches[0][4])
print dataMatches
......
Code for main that definitely does not work:
if __name__ == '__main__':
MyKeyboardListener() #keyboard listener, works fine
SnakeApp().run()
socketIO = SocketIO('10.0.0.4',8080,Namespace)
socketIO.wait()
I tried the following multithreading, but it didn't work:
if __name__ == '__main__':
MyKeyboardListener() #keyboard listener, works fine
threading.Thread(target = SnakeApp().run).start() #results in abort
socketIO = SocketIO('10.0.0.4',8080,Namespace)
socketIO.wait()
The above code results in making program to abort with error message :"Fatal Python error: (pygame parachute) Segmentation Fault
Aborted"
I also tried another multithreading method but it didn't work as well. This is really frustrating. Is there any way to let game loop and socketIO's wait loop run at the same time? or I just missed something?
UPDATE: working code for main:
def connect_socket():
socketIO = SocketIO('10.0.0.4',8080,Namespace)
socketIO.wait()
if __name__ == '__main__':
MyKeyboardListener() #keyboard listener, works fine
socketThread = threading.Thread(target = connect_socket) #creat thread for socket
socketThread.daemon = True #set daemon flag
socketThread.start()
SnakeApp().run
You should run the kivy main loop in the primary thread, and the socket listing in a secondary thread (reverse of your second try that didn't work).
But it will leave your app hanging when you simply close it, because the secondary thread will keep it alive despite the primary thread being dead.
The easiest solution to this problem is to start the secondary thread with a daemon = True flag, so it will be killed as soon as the primary thread is dead.

Python - Running Autobahn|Python asyncio websocket server in a separate subprocess or thread

I have a tkinter based GUI program running in Python 3.4.1. I have several threads running in the program to get JSON data from various urls. I am wanting to add some WebSocket functionality to be able to allow program to act as a server and allow several clients to connect to it over a WebSocket and exchange other JSON data.
I am attempting to use the Autobahn|Python WebSocket server for asyncio.
I first tried to run the asyncio event loop in a separate thread under the GUI program. However, every attempt gives 'AssertionError: There is no current event loop in thread 'Thread-1'.
I then tried spawning a process with the standard library multiprocessing package that ran the asyncio event loop in another Process. When I try this I don't get any exception but the WebSocket server doesn't start either.
Is it even possible to run an asyncio event loop in a subprocess from another Python program?
Is there even a way to integrate an asyncio event loop into a currently multithreaded/tkinter program?
UPDATE
Below is the actual code I am trying to run for an initial test.
from autobahn.asyncio.websocket import WebSocketServerProtocol
from autobahn.asyncio.websocket import WebSocketServerFactory
import asyncio
from multiprocessing import Process
class MyServerProtocol(WebSocketServerProtocol):
def onConnect(self, request):
print("Client connecting: {0}".format(request.peer))
def onOpen(self):
print("WebSocket connection open.")
def onMessage(self, payload, isBinary):
if isBinary:
print("Binary message received: {0} bytes".format(len(payload)))
else:
print("Text message received: {0}".format(payload.decode('utf8')))
## echo back message verbatim
self.sendMessage(payload, isBinary)
def onClose(self, wasClean, code, reason):
print("WebSocket connection closed: {0}".format(reason))
def start_server():
factory = WebSocketServerFactory("ws://10.241.142.27:6900", debug = False)
factory.protocol = MyServerProtocol
loop = asyncio.get_event_loop()
coro = loop.create_server(factory, '10.241.142.27', 6900)
server = loop.run_until_complete(coro)
loop.run_forever()
server.close()
loop.close()
websocket_server_process = Process(target = start_server)
websocket_server_process.start()
Most of it is straight from the Autobahn|Python example code for asyncio. If I try to run it as a Process it doesn't do anything, no client can connect to it, if I run netstat -a there is no port 6900 being used. If just use start_server() in the main program it creates the WebSocket Server.
First, you're getting AssertionError: There is no current event loop in thread 'Thread-1'. because asyncio requires each thread in your program to have its own event loop, but it will only automatically create an event loop for you in the main thread. So if you call asyncio.get_event_loop once in the main thread it will automatically create a loop object and set it as the default for you, but if you call it again in a child thread, you'll get that error. Instead, you need to explicitly create/set the event loop when the thread starts:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
Once you've done that, you should be able to use get_event_loop() in that specific thread.
It is possible to start an asyncio event loop in a subprocess started via multiprocessing:
import asyncio
from multiprocessing import Process
#asyncio.coroutine
def coro():
print("hi")
def worker():
loop = asyncio.get_event_loop()
loop.run_until_complete(coro())
if __name__ == "__main__":
p = Process(target=worker)
p.start()
p.join()
Output:
hi
The only caveat is that if you start an event loop in the parent process as well as the child, you need to explicitly create/set a new event loop in the child if you're on a Unix platform (due to a bug in Python). It should work fine on Windows, or if you use the 'spawn' multiprocessing context.
I think it should be possible to start an asyncio event loop in a background thread (or process) of your Tkinter application and have both the tkinter and asyncio event loop run side-by-side. You'll only run into issues if you try to update the GUI from the background thread/process.
The answer by #dano might be correct, but creates an new process which is unnessesary in most situations.
I found this question on Google because i had the same issue myself. I have written an application where i wanted an websocket api to not run on the main thread and this caused your issue.
I found my alternate sollution by simply reading about event loops on the python documentation and found the asyncio.new_event_loop and asyncio.set_event_loop functions which solved this issue.
I didn't use AutoBahn but the pypi websockets library, and here's my solution
import websockets
import asyncio
import threading
class WebSocket(threading.Thread):
#asyncio.coroutine
def handler(self, websocket, path):
name = yield from websocket.recv()
print("< {}".format(name))
greeting = "Hello {}!".format(name)
yield from websocket.send(greeting)
print("> {}".format(greeting))
def run(self):
start_server = websockets.serve(self.handler, '127.0.0.1', 9091)
eventloop = asyncio.new_event_loop()
asyncio.set_event_loop(eventloop)
eventloop.run_until_complete(start_server)
eventloop.run_forever()
if __name__ == "__main__":
ws = WebSocket()
ws.start()
"Is there even a way to integrate an asyncio event loop into a currently multithreaded/tkinter program?"
Yes, run your tkinter program with an asyncio event loop. Proof of concept.
'''Proof of concept integrating asyncio and tk loops.
Terry Jan Reedy
Run with 'python -i' or from IDLE editor to keep tk window alive.
'''
import asyncio
import datetime as dt
import tkinter as tk
loop = asyncio.get_event_loop()
root = tk.Tk()
# Combine 2 event loop examples from BaseEventLoop doc.
# Add button to prove that gui remain responsive between time updates.
# Prints statements are only for testing.
def flipbg(widget, color):
bg = widget['bg']
print('click', bg, loop.time())
widget['bg'] = color if bg == 'white' else 'white'
hello = tk.Label(root)
flipper = tk.Button(root, text='Change hello background', bg='yellow',
command=lambda: flipbg(hello, 'red'))
time = tk.Label(root)
hello.pack()
flipper.pack()
time.pack()
def hello_world(loop):
hello['text'] = 'Hello World'
loop.call_soon(hello_world, loop)
def display_date(end_time, loop):
print(dt.datetime.now())
time['text'] = dt.datetime.now()
if (loop.time() + 1.0) < end_time:
loop.call_later(1, display_date, end_time, loop)
else:
loop.stop()
end_time = loop.time() + 10.1
loop.call_soon(display_date, end_time, loop)
# Replace root.mainloop with these 4 lines.
def tk_update():
root.update()
loop.call_soon(tk_update) # or loop.call_later(delay, tk_update)
# Initialize loop before each run_forever or run_until_complete call
tk_update()
loop.run_forever()
I have experimentally run IDLE with those 4 extra lines, with a slowdown only noticeable when syntax highlighting 1000s of lines.

Categories

Resources